blob: a2262223b294a57e39f8d43c41c1ea24c152fdf1 [file] [log] [blame] [edit]
/*
* Copyright (c) 2022 Apple Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//======================================================================================================================
// MARK: - Headers
#include "dns_obj_log.h"
#include "dns_obj_domain_name.h"
#include "dns_obj_crypto.h"
#include "domain_name_labels.h"
#include "base_encoding.h"
#include "string.h" // For memcpy().
#include "dns_assert_macros.h"
#include "mdns_strict.h"
//======================================================================================================================
// MARK: - DNS Domain Name Kind Definition
struct dns_obj_domain_name_s {
struct ref_count_obj_s base; // The reference count and kind support base.
union {
const uint8_t * const_labels; // The pointer to a domain name labels that is valid for the life time of this domain name object.
uint8_t * allocated_labels; // The pointer to a domain name labels that is allocated when initializing this domain name object.
} labels_u;
size_t length; // The length of the domain name labels.
uint32_t hash_value; // The hash value of the domain name labels.
bool hash_computed; // The boolean value to indicate if the hash value has been computed for this domain name object.
bool allocated_memory; // Indicate whether the domain name labels point to an external memory or is allocated.
dns_obj_domain_name_t nsec3_hashed_name; // The hashed domain name if it has been set.
};
DNS_OBJECT_DEFINE_FULL(domain_name); // Define domain name object as a DNSSEC object.
//======================================================================================================================
// MARK: - Local Prototypes
static dns_obj_error_t
_dns_obj_domain_name_init_fields(dns_obj_domain_name_t domain_name, const uint8_t *labels, bool allocate_memory);
static dns_obj_error_t
dns_obj_domain_name_set_nsec3_hashed_name(dns_obj_domain_name_t NONNULL me, dns_obj_rr_nsec3_t NONNULL nsec3);
//======================================================================================================================
// MARK: - DNSSEC Domain Name Public Methods
dns_obj_domain_name_t
dns_obj_domain_name_create_with_labels(const uint8_t * const labels, const bool allocate_memory,
dns_obj_error_t * const out_error)
{
dns_obj_error_t err;
dns_obj_domain_name_t domain_name = NULL;
dns_obj_domain_name_t obj = NULL;
obj = _dns_obj_domain_name_new();
require_action(obj != NULL, exit, err = DNS_OBJ_ERROR_NO_MEMORY);
err = _dns_obj_domain_name_init_fields(obj, labels, allocate_memory);
require_noerr(err, exit);
domain_name = obj;
obj = NULL;
err = DNS_OBJ_ERROR_NO_ERROR;
exit:
if (out_error != NULL) {
*out_error = err;
}
MDNS_DISPOSE_DNS_OBJ(obj);
return domain_name;
}
//======================================================================================================================
dns_obj_domain_name_t
dns_obj_domain_name_create_concatenation(const dns_obj_domain_name_t front_domain,
const dns_obj_domain_name_t end_domain, dns_obj_error_t * const out_error)
{
return dns_obj_domain_name_create_concatenation_with_subdomain(dns_obj_domain_name_get_labels(front_domain),
end_domain, out_error);
}
//======================================================================================================================
dns_obj_domain_name_t
dns_obj_domain_name_create_concatenation_with_subdomain(const uint8_t * const subdomain_in_labels,
const dns_obj_domain_name_t end_domain, dns_obj_error_t * const out_error)
{
dns_obj_error_t err;
uint8_t concatenation_in_labels[MAX_DOMAIN_NAME];
dns_obj_domain_name_t concatenation = NULL;
domain_name_labels_concatenate(subdomain_in_labels, dns_obj_domain_name_get_labels(end_domain),
concatenation_in_labels, sizeof(concatenation_in_labels), &err);
require_noerr(err, exit);
concatenation = dns_obj_domain_name_create_with_labels(concatenation_in_labels, true, &err);
require_noerr(err, exit);
exit:
if (out_error != NULL) {
*out_error = err;
}
return concatenation;
}
//======================================================================================================================
// See <https://datatracker.ietf.org/doc/html/rfc4035#section-5.3.2> for how canonical name is calculated.
dns_obj_domain_name_t
dns_obj_domain_name_create_canonical(const dns_obj_domain_name_t me, const uint8_t rrsig_labels,
dns_obj_error_t * const out_error)
{
dns_obj_error_t err;
dns_obj_domain_name_t canonical = NULL;
dns_obj_domain_name_t rightmost = NULL;
// let fqdn = RRset's fully qualified domain name in canonical form
const dns_obj_domain_name_t fqdn = me;
// let fqdn_labels = Label count of the fqdn above.
const size_t fqdn_labels = dns_obj_domain_name_get_label_count(fqdn);
// if rrsig_labels > fqdn_labels, the RRSIG RR did not pass the necessary validation checks and MUST NOT be used to
// authenticate this RRset.
require_action(rrsig_labels <= fqdn_labels, exit, err = DNS_OBJ_ERROR_RANGE_ERR);
if (rrsig_labels == fqdn_labels) {
// if rrsig_labels = fqdn_labels,
// result = fqdn
canonical = me;
dns_obj_retain(canonical);
} else {
// if rrsig_labels < fqdn_labels,
// result = "*." | the rightmost rrsig_label labels of the fqdn
const uint8_t asterisk_labels[] = {1, '*', 0};
rightmost = dns_obj_domain_name_copy_parent_domain(fqdn, fqdn_labels - rrsig_labels, &err);
require_noerr(err, exit);
canonical = dns_obj_domain_name_create_concatenation_with_subdomain(asterisk_labels, rightmost, &err);
require_noerr(err, exit);
}
err = DNS_OBJ_ERROR_NO_ERROR;
exit:
if (out_error != NULL) {
*out_error = err;
}
MDNS_DISPOSE_DNS_OBJ(rightmost);
return canonical;
}
//======================================================================================================================
dns_obj_domain_name_t
dns_obj_domain_name_create_with_cstring(const char * const name_cstring, dns_obj_error_t * const out_error)
{
dns_obj_error_t err;
uint8_t labels[MAX_DOMAIN_NAME];
dns_obj_domain_name_t name = NULL;
err = cstring_to_domain_name_labels(name_cstring, labels, NULL);
require_noerr(err, exit);
name = dns_obj_domain_name_create_with_labels(labels, true, &err);
require_noerr(err, exit);
exit:
if (out_error != NULL) {
*out_error = err;
}
return name;
}
//======================================================================================================================
dns_obj_domain_name_t
dns_obj_domain_name_create_copy(const dns_obj_domain_name_t name, dns_obj_error_t * const out_error)
{
return dns_obj_domain_name_create_with_labels(dns_obj_domain_name_get_labels(name), true, out_error);
}
//======================================================================================================================
const uint8_t *
dns_obj_domain_name_get_labels(const dns_obj_domain_name_t me)
{
return me->allocated_memory ? me->labels_u.allocated_labels : me->labels_u.const_labels;
}
//======================================================================================================================
size_t
dns_obj_domain_name_get_label_count(const dns_obj_domain_name_t me)
{
const uint8_t * const my_labels = dns_obj_domain_name_get_labels(me);
return domain_name_labels_count_label(my_labels);
}
//======================================================================================================================
size_t
dns_obj_domain_name_get_length(const dns_obj_domain_name_t me)
{
return me->length;
}
//======================================================================================================================
dns_obj_error_t
dns_obj_domain_name_to_cstring(const dns_obj_domain_name_t me, char out_name_cstring[static const MAX_ESCAPED_DOMAIN_NAME])
{
return domain_name_labels_to_cstring(dns_obj_domain_name_get_labels(me), dns_obj_domain_name_get_length(me), out_name_cstring);
}
//======================================================================================================================
bool
dns_obj_domain_name_is_root(const dns_obj_domain_name_t me)
{
const uint8_t * const my_labels = dns_obj_domain_name_get_labels(me);
return my_labels[0] == 0;
}
//======================================================================================================================
bool
dns_obj_domain_name_is_single_label(const dns_obj_domain_name_t me)
{
const uint8_t * const my_labels = dns_obj_domain_name_get_labels(me);
return ((my_labels[0] + 2) == dns_obj_domain_name_get_length(me));
}
//======================================================================================================================
bool
dns_obj_domain_name_is_sub_domain_of(const dns_obj_domain_name_t me, const dns_obj_domain_name_t parent)
{
const uint8_t * const my_labels = dns_obj_domain_name_get_labels(me);
const uint8_t * const parents_labels = dns_obj_domain_name_get_labels(parent);
return domain_name_labels_is_sub_labels_of(my_labels, parents_labels);
}
//======================================================================================================================
dns_obj_domain_name_t
dns_obj_domain_name_copy_parent_domain(const dns_obj_domain_name_t me, const size_t index, dns_obj_error_t * const out_error)
{
dns_obj_error_t err;
dns_obj_domain_name_t parent = NULL;
const uint8_t * const my_labels = dns_obj_domain_name_get_labels(me);
const uint8_t * const parents_labels = domain_name_labels_get_parent(my_labels, index);
require_action(parents_labels != NULL, exit, err = DNS_OBJ_ERROR_RANGE_ERR);
parent = dns_obj_domain_name_create_with_labels(parents_labels, true, &err);
require_noerr(err, exit);
err = DNS_OBJ_ERROR_NO_ERROR;
exit:
if (out_error != NULL) {
*out_error = err;
}
return parent;
}
//======================================================================================================================
dns_obj_domain_name_t
dns_obj_domain_name_copy_closest_common_ancestor(const dns_obj_domain_name_t me,
const dns_obj_domain_name_t other, dns_obj_error_t * const out_error)
{
// The closest common ancestor of "wwwtest.ietf.org." and "xml2rfc.ietf.org." is "ietf.org.".
// The closest common ancestor of "*.wildcard.dnssec.qdeng.io." and "a.wildcard.dnssec.qdeng.io." is "wildcard.dnssec.qdeng.io.".
const uint8_t * const closest_common_ancestor_labels =
domain_name_labels_get_closest_common_ancestor(dns_obj_domain_name_get_labels(me),
dns_obj_domain_name_get_labels(other));
dns_obj_error_t err;
const dns_obj_domain_name_t closest_common_ancestor =
dns_obj_domain_name_create_with_labels(closest_common_ancestor_labels, true, &err);
if (out_error != NULL) {
*out_error = err;
}
return closest_common_ancestor;
}
//======================================================================================================================
bool
dns_obj_domain_name_is_wildcard_domain_name(const dns_obj_domain_name_t NONNULL me)
{
const uint8_t asterisk_label[] = {1, '*'};
if (dns_obj_domain_name_get_length(me) <= sizeof(asterisk_label)) {
verify(dns_obj_domain_name_is_root(me));
return false;
}
const uint8_t * const wildcard_in_labels = dns_obj_domain_name_get_labels(me);
const bool is_wildcard_domain_name = (memcmp(wildcard_in_labels, asterisk_label, sizeof(asterisk_label)) == 0);
return is_wildcard_domain_name;
}
//======================================================================================================================
bool
dns_obj_domain_name_is_a_wildcard_expansion(const dns_obj_domain_name_t me,
const dns_obj_domain_name_t wildcard)
{
// An expansion of wildcard (me) cannot be a wildcard domain name.
if (dns_obj_domain_name_is_wildcard_domain_name(me)) {
return false;
}
// A wildcard has to be a wildcard domain name.
if (!dns_obj_domain_name_is_wildcard_domain_name(wildcard)) {
return false;
}
// Wildcard expansion can only happen on the leading label.
const uint8_t * const closest_encloser = domain_name_labels_get_parent(dns_obj_domain_name_get_labels(wildcard), 1);
// closest_encloser is ensured to be non-null because the wildcard check above.
// www.appleweb.apple.com. is an expansion of *, *.com., *.apple.com., *.appleweb.apple.com.
const uint8_t * const my_name_in_labels = dns_obj_domain_name_get_labels(me);
return domain_name_labels_is_sub_labels_of(my_name_in_labels, closest_encloser);
}
//======================================================================================================================
dns_obj_error_t
dns_obj_domain_name_set_nsec3_hashed_name_with_params(const dns_obj_domain_name_t me, const uint8_t algorithm,
const uint16_t iterations, const uint8_t * const salt, const uint8_t salt_length, const uint8_t * const zone_domain)
{
dns_obj_error_t err;
digest_type_t digest_type;
uint8_t *lower_case_labels = NULL;
switch (algorithm) {
case NSEC3_HASH_ALGORITHM_SHA_1:
digest_type = DIGEST_SHA_1;
err = DNS_OBJ_ERROR_NO_ERROR;
break;
default:
digest_type = DIGEST_UNSUPPORTED;
err = DNS_OBJ_ERROR_UNSUPPORTED_ERR;
break;
}
require_noerr(err, exit);
// Hash is calculated with all lower case domain name.
lower_case_labels = domain_name_labels_create(dns_obj_domain_name_get_labels(me), true, &err);
require_noerr(err, exit);
const size_t lower_case_labels_len = domain_name_labels_length(lower_case_labels);
// Calculate the binary format of the hash value.
uint8_t digest[MAX_HASHED_NAME_BUFF_SIZE];
err = dns_obj_compute_nsec3_digest(digest_type, lower_case_labels, lower_case_labels_len, iterations, salt, salt_length, digest);
const size_t digest_len = dns_obj_data_compute_digest_get_output_size(digest_type);
// Convert the binary hash value to a base32 hex string, and convert it to a domain name labels.
char base32_hex[1 + BASE32_HEX_OUTPUT_SIZE(MAX_HASHED_NAME_OUTPUT_SIZE)];
const size_t base32_encoded_str_len = base_x_get_encoded_string_length(base_encoding_type_base32_hex_without_padding, digest_len);
require_action(base32_encoded_str_len <= MAX_DOMAIN_LABEL, exit, err = DNS_OBJ_ERROR_OVER_RUN);
base32_hex[0] = (char)base32_encoded_str_len;
base_x_encode(base_encoding_type_base32_hex_without_padding, digest, digest_len, base32_hex + 1);
const uint8_t * const base32_hex_as_labels = (uint8_t *)base32_hex;
// Append the zone domain after the base32 hex encoded label.
uint8_t my_nsec3_hashed_name_in_labels[MAX_DOMAIN_NAME];
domain_name_labels_concatenate(base32_hex_as_labels, zone_domain, my_nsec3_hashed_name_in_labels, sizeof(my_nsec3_hashed_name_in_labels), &err);
require_noerr(err, exit);
// Create the domain name object based on the final domain name labels.
dns_obj_domain_name_t my_nsec3_hashed_name = dns_obj_domain_name_create_with_labels(my_nsec3_hashed_name_in_labels, true, &err);
require_noerr(err, exit);
me->nsec3_hashed_name = my_nsec3_hashed_name;
err = DNS_OBJ_ERROR_NO_ERROR;
exit:
mdns_free(lower_case_labels);
return err;
}
//======================================================================================================================
dns_obj_domain_name_t
dns_obj_domain_name_get_nsec3_hashed_name(const dns_obj_domain_name_t me, const dns_obj_rr_nsec3_t nsec3)
{
dns_obj_domain_name_t hashed_name = me->nsec3_hashed_name;
if (hashed_name == NULL && nsec3 != NULL) {
const dns_obj_error_t err = dns_obj_domain_name_set_nsec3_hashed_name(me, nsec3);
require_noerr_return_value(err, NULL);
hashed_name = me->nsec3_hashed_name;
}
return hashed_name;
}
//======================================================================================================================
void
dns_obj_domain_name_clear_nsec3_hashed_name(const dns_obj_domain_name_t me)
{
MDNS_DISPOSE_DNS_OBJ(me->nsec3_hashed_name);
}
//======================================================================================================================
// MARK: - DNSSEC Domain Name Private Methods
static compare_result_t
_dns_obj_domain_name_compare(const dns_obj_domain_name_t me, const dns_obj_domain_name_t other,
const bool check_equality_only)
{
const uint8_t * const my_labels = dns_obj_domain_name_get_labels(me);
const uint8_t * const others_labels = dns_obj_domain_name_get_labels(other);
if (me == other) {
return compare_result_equal;
}
if (me->length == other->length) {
if (!me->hash_computed) {
me->hash_value = domain_name_labels_compute_hash(my_labels);
me->hash_computed = true;
}
if (!other->hash_computed) {
other->hash_value = domain_name_labels_compute_hash(others_labels);
other->hash_computed = true;
}
if (me->hash_value != other->hash_value && check_equality_only) {
// If the hash values are not equal, the two domain name object cannot be equal.
return compare_result_notequal;
}
} else { // me->length != other->length
if (check_equality_only) {
// If the lengths are not equal, the two domain name object cannot be equal.
return compare_result_notequal;
}
// Continue if we want know the exact order of the two objects.
}
return domain_name_labels_canonical_compare(my_labels, others_labels, check_equality_only);
}
//======================================================================================================================
static void
_dns_obj_domain_name_finalize(const dns_obj_domain_name_t me)
{
if (me->allocated_memory) {
// Only free the domain name labels when the memory associated with it is allocated.
mdns_free(me->labels_u.allocated_labels);
}
MDNS_DISPOSE_DNS_OBJ(me->nsec3_hashed_name);
}
//======================================================================================================================
// MARK: - Local Functions
static dns_obj_error_t
_dns_obj_domain_name_init_fields(const dns_obj_domain_name_t me, const uint8_t * labels,
bool allocate_memory)
{
dns_obj_error_t err;
uint8_t *allocated_labels = NULL;
size_t length;
if (!allocate_memory && !domain_name_labels_contains_upper_case(labels)) {
// If the original domain name labels contains upper case characters, to make it consistent with the following
// name manipulation that requires all lower-case labels, we convert it to lower case by allocating a new memory
// region for it.
me->labels_u.const_labels = labels;
length = domain_name_labels_length(labels);
me->allocated_memory = false;
} else {
allocated_labels = domain_name_labels_create(labels, true, &err);
require_noerr(err, exit);
length = domain_name_labels_length(allocated_labels);
me->labels_u.allocated_labels = allocated_labels;
allocated_labels = NULL;
me->allocated_memory = true;
}
me->length = length;
me->hash_value = 0;
me->hash_computed = false;
me->nsec3_hashed_name = NULL;
err = DNS_OBJ_ERROR_NO_ERROR;
exit:
mdns_free(allocated_labels);
return err;
}
//======================================================================================================================
static dns_obj_error_t
dns_obj_domain_name_set_nsec3_hashed_name(const dns_obj_domain_name_t me, const dns_obj_rr_nsec3_t nsec3)
{
dns_obj_error_t err;
uint8_t salt_length;
const uint8_t algorithm = dns_obj_rr_nsec3_get_hash_algorithm(nsec3);
const uint16_t iterations = dns_obj_rr_nsec3_get_iterations(nsec3);
const uint8_t * const salt = dns_obj_rr_nsec3_get_salt(nsec3, &salt_length);
const dns_obj_domain_name_t current_owner_name = dns_obj_rr_nsec3_get_current_owner_name(nsec3);
require_action(dns_obj_domain_name_get_label_count(current_owner_name) >= 1, exit, err = DNS_OBJ_ERROR_MALFORMED_ERR);
const uint8_t * const zone_domain = domain_name_labels_get_parent(dns_obj_domain_name_get_labels(current_owner_name), 1);
err = dns_obj_domain_name_set_nsec3_hashed_name_with_params(me, algorithm, iterations, salt, salt_length, zone_domain);
exit:
return err;
}