| /* towire.c |
| * |
| * Copyright (c) 2018-2021 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 |
| * |
| * http://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. |
| * |
| * DNS to-wire wire-format functions. |
| * |
| * These are really simple functions for constructing DNS messages in wire format. |
| * The flow is that there is a transaction structure which contains pointers to both |
| * a message output buffer and a response input buffer. The structure is initialized, |
| * and then the various wire format functions are called repeatedly to store data. |
| * If an error occurs during this process, it's okay to just keep going, because the |
| * error is recorded in the transaction; once all of the copy-in functions have been |
| * called, the error status can be checked once at the end. |
| */ |
| |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <errno.h> |
| #ifndef THREAD_DEVKIT_ADK |
| #include <arpa/inet.h> |
| #endif |
| #include <stdlib.h> |
| |
| #include "srp.h" |
| #include "dns-msg.h" |
| #include "srp-crypto.h" |
| |
| #ifndef NO_CLOCK |
| #include <sys/time.h> |
| #endif |
| |
| static int |
| dns_parse_label(const char *cur, const char *NONNULL *NONNULL nextp, uint8_t *NONNULL lenp, uint8_t *NONNULL buf, |
| ssize_t max) |
| { |
| const char *end; |
| int tlen; |
| const char *s; |
| uint8_t *t; |
| |
| end = strchr(cur, '.'); |
| if (end == NULL) { |
| end = cur + strlen(cur); |
| if (end == cur) { |
| *lenp = 0; |
| *nextp = NULL; |
| return 0; |
| } |
| *nextp = NULL; |
| } else { |
| if (end == cur) { |
| return EINVAL; |
| } |
| *nextp = end + 1; |
| } |
| |
| // Figure out the length of the label after escapes have been converted. |
| tlen = 0; |
| for (s = cur; s < end; s++) { |
| if (*s == '\\') { |
| if (s + 4 <= end) { |
| tlen++; |
| s += 3; |
| } else { |
| tlen++; |
| } |
| } else { |
| tlen++; |
| } |
| } |
| |
| // Is there no space? |
| |
| if (tlen >= max) { |
| return ENOBUFS; |
| } |
| |
| // Is the label too long? |
| if (end - cur > DNS_MAX_LABEL_SIZE) { |
| return ENAMETOOLONG; |
| } |
| |
| // Store the label length |
| *lenp = (uint8_t)(tlen); |
| |
| // Store the label. |
| t = buf; |
| for (s = cur; s < end; s++) { |
| if (*s == '\\') { |
| if (s + 4 <= end) { |
| int v0 = s[1] - '0'; |
| int v1 = s[2] - '0'; |
| int v2 = s[3] - '0'; |
| int val = v0 * 100 + v1 * 10 + v2; |
| if (val < 255) { |
| *t++ = (uint8_t)val; |
| s += 3; |
| } else { |
| return EINVAL; |
| } |
| } else { |
| return EINVAL; |
| } |
| } else { |
| *t++ = (uint8_t)*s; |
| } |
| } |
| return 0; |
| } |
| |
| // Convert a name to wire format. Does not store the root label (0) at the end. Does not support binary labels. |
| void |
| dns_name_to_wire_(dns_name_pointer_t *NULLABLE r_pointer, dns_towire_state_t *NONNULL txn, |
| const char *NONNULL name, int line) |
| { |
| const char *next, *cur; |
| int status; |
| dns_name_pointer_t np; |
| |
| if (!txn->error) { |
| memset(&np, 0, sizeof np); |
| np.message_start = (uint8_t *)txn->message; |
| np.name_start = txn->p; |
| |
| cur = name; |
| do { |
| // Note that nothing is stored through txn->p until dns_name_parse has verified that |
| // there is space in the buffer for the label as well as the length. |
| status = dns_parse_label(cur, &next, txn->p, txn->p + 1, txn->lim - txn->p - 1); |
| if (status) { |
| if (status == ENOBUFS) { |
| txn->truncated = true; |
| } |
| txn->error = (unsigned)status; |
| txn->line = line; |
| return; |
| } |
| |
| // Don't use the root label if it was parsed. |
| if (*txn->p != 0) { |
| np.num_labels++; |
| np.length += 1 + *txn->p; |
| txn->p = txn->p + *txn->p + 1; |
| cur = next; |
| } |
| } while (next != NULL); |
| |
| if (np.length > DNS_MAX_NAME_SIZE) { |
| txn->error = ENAMETOOLONG; |
| txn->line = line; |
| return; |
| } |
| if (r_pointer != NULL) { |
| *r_pointer = np; |
| } |
| } |
| } |
| |
| // Like dns_name_to_wire, but includes the root label at the end. |
| void |
| dns_full_name_to_wire_(dns_name_pointer_t *NULLABLE r_pointer, dns_towire_state_t *NONNULL txn, |
| const char *NONNULL name, int line) |
| { |
| dns_name_pointer_t np; |
| if (!txn->error) { |
| memset(&np, 0, sizeof np); |
| dns_name_to_wire(&np, txn, name); |
| if (!txn->error) { |
| if (txn->p + 1 >= txn->lim) { |
| txn->error = ENOBUFS; |
| txn->truncated = true; |
| txn->line = line; |
| return; |
| } |
| *txn->p++ = 0; |
| np.num_labels++; |
| np.length += 1; |
| if (np.length > DNS_MAX_NAME_SIZE) { |
| txn->error = ENAMETOOLONG; |
| txn->line = line; |
| return; |
| } |
| if (r_pointer) { |
| *r_pointer = np; |
| } |
| } |
| } |
| } |
| |
| // Store a pointer to a name that's already in the message. |
| void |
| dns_pointer_to_wire_(dns_name_pointer_t *NULLABLE r_pointer, dns_towire_state_t *NONNULL txn, |
| dns_name_pointer_t *NONNULL pointer, int line) |
| { |
| if (!txn->error) { |
| uint16_t offset = (uint16_t)(pointer->name_start - pointer->message_start); |
| if (offset > DNS_MAX_POINTER) { |
| txn->error = ETOOMANYREFS; |
| txn->line = line; |
| return; |
| } |
| if (txn->p + 2 >= txn->lim) { |
| txn->error = ENOBUFS; |
| txn->truncated = true; |
| txn->line = line; |
| return; |
| } |
| *txn->p++ = 0xc0 | (offset >> 8); |
| *txn->p++ = offset & 0xff; |
| if (r_pointer) { |
| r_pointer->num_labels += pointer->num_labels; |
| r_pointer->length += pointer->length + 1; |
| if (r_pointer->length > DNS_MAX_NAME_SIZE) { |
| txn->error = ENAMETOOLONG; |
| txn->line = line; |
| return; |
| } |
| } |
| } |
| } |
| |
| void |
| dns_u8_to_wire_(dns_towire_state_t *NONNULL txn, uint8_t val, int line) |
| { |
| if (!txn->error) { |
| if (txn->p + 1 >= txn->lim) { |
| txn->error = ENOBUFS; |
| txn->truncated = true; |
| txn->line = line; |
| return; |
| } |
| *txn->p++ = val; |
| } |
| } |
| |
| // Store a 16-bit integer in network byte order |
| void |
| dns_u16_to_wire_(dns_towire_state_t *NONNULL txn, uint16_t val, int line) |
| { |
| if (!txn->error) { |
| if (txn->p + 2 >= txn->lim) { |
| txn->error = ENOBUFS; |
| txn->truncated = true; |
| txn->line = line; |
| return; |
| } |
| *txn->p++ = val >> 8; |
| *txn->p++ = val & 0xff; |
| } |
| } |
| |
| void |
| dns_u32_to_wire_(dns_towire_state_t *NONNULL txn, uint32_t val, int line) |
| { |
| if (!txn->error) { |
| if (txn->p + 4 >= txn->lim) { |
| txn->error = ENOBUFS; |
| txn->truncated = true; |
| txn->line = line; |
| return; |
| } |
| *txn->p++ = val >> 24; |
| *txn->p++ = (val >> 16) & 0xff; |
| *txn->p++ = (val >> 8) & 0xff; |
| *txn->p++ = val & 0xff; |
| } |
| } |
| |
| void |
| dns_u64_to_wire_(dns_towire_state_t *NONNULL txn, uint64_t val, int line) |
| { |
| if (!txn->error) { |
| if (txn->p + 8 >= txn->lim) { |
| txn->error = ENOBUFS; |
| txn->truncated = true; |
| txn->line = line; |
| return; |
| } |
| *txn->p++ = val >> 56; |
| *txn->p++ = (val >> 48) & 0xff; |
| *txn->p++ = (val >> 40) & 0xff; |
| *txn->p++ = (val >> 32) & 0xff; |
| *txn->p++ = (val >> 24) & 0xff; |
| *txn->p++ = (val >> 16) & 0xff; |
| *txn->p++ = (val >> 8) & 0xff; |
| *txn->p++ = val & 0xff; |
| } |
| } |
| |
| void |
| dns_ttl_to_wire_(dns_towire_state_t *NONNULL txn, int32_t val, int line) |
| { |
| if (!txn->error) { |
| dns_u32_to_wire_(txn, (uint32_t)val, line); |
| } |
| } |
| |
| void |
| dns_rdlength_begin_(dns_towire_state_t *NONNULL txn, int line) |
| { |
| if (!txn->error) { |
| if (txn->p + 2 >= txn->lim) { |
| txn->error = ENOBUFS; |
| txn->truncated = true; |
| txn->line = line; |
| return; |
| } |
| if (txn->p_rdlength != NULL) { |
| txn->error = EINVAL; |
| txn->line = line; |
| return; |
| } |
| txn->p_rdlength = txn->p; |
| txn->p += 2; |
| } |
| } |
| |
| void |
| dns_rdlength_end_(dns_towire_state_t *NONNULL txn, int line) |
| { |
| ssize_t rdlength; |
| if (!txn->error) { |
| if (txn->p_rdlength == NULL) { |
| txn->error = EINVAL; |
| txn->line = line; |
| return; |
| } |
| rdlength = txn->p - txn->p_rdlength - 2; |
| txn->p_rdlength[0] = (uint8_t)(rdlength >> 8); |
| txn->p_rdlength[1] = (uint8_t)(rdlength & 0xff); |
| txn->p_rdlength = NULL; |
| } |
| } |
| |
| #ifndef THREAD_DEVKIT_ADK |
| void |
| dns_rdata_a_to_wire_(dns_towire_state_t *NONNULL txn, const char *NONNULL ip_address, int line) |
| { |
| if (!txn->error) { |
| if (txn->p + 4 >= txn->lim) { |
| txn->error = ENOBUFS; |
| txn->truncated = true; |
| txn->line = line; |
| return; |
| } |
| if (!inet_pton(AF_INET, ip_address, txn->p)) { |
| txn->error = EINVAL; |
| txn->line = line; |
| return; |
| } |
| txn->p += 4; |
| } |
| } |
| |
| void |
| dns_rdata_aaaa_to_wire_(dns_towire_state_t *NONNULL txn, const char *NONNULL ip_address, int line) |
| { |
| if (!txn->error) { |
| if (txn->p + 16 >= txn->lim) { |
| txn->error = ENOBUFS; |
| txn->truncated = true; |
| txn->line = line; |
| return; |
| } |
| if (!inet_pton(AF_INET6, ip_address, txn->p)) { |
| txn->error = EINVAL; |
| txn->line = line; |
| return; |
| } |
| txn->p += 16; |
| } |
| } |
| #endif |
| |
| uint16_t |
| dns_rdata_key_to_wire_(dns_towire_state_t *NONNULL txn, unsigned key_type, unsigned name_type, |
| uint8_t signatory, srp_key_t *key, int line) |
| { |
| size_t key_len = srp_pubkey_length(key), copied_len; |
| uint8_t *rdata = txn->p; |
| uint32_t key_tag; |
| int i; |
| ssize_t rdlen; |
| |
| if (!txn->error) { |
| if (key_type > 3 || name_type > 3 || signatory > 15) { |
| txn->error = EINVAL; |
| txn->line = line; |
| return 0; |
| } |
| if (txn->p + key_len + 4 >= txn->lim) { |
| txn->error = ENOBUFS; |
| txn->truncated = true; |
| txn->line = line; |
| return 0; |
| } |
| *txn->p++ = (uint8_t)((key_type << 6) | name_type); |
| *txn->p++ = signatory; |
| *txn->p++ = 3; // protocol type is always 3 |
| *txn->p++ = srp_key_algorithm(key); |
| copied_len = srp_pubkey_copy(txn->p, key_len, key); |
| if (copied_len == 0) { |
| txn->error = EINVAL; |
| txn->line = line; |
| return 0; |
| } |
| txn->p += key_len; |
| } |
| rdlen = txn->p - rdata; |
| |
| // Compute the key tag |
| key_tag = 0; |
| for (i = 0; i < rdlen; i++) { |
| key_tag += (i & 1) ? rdata[i] : (uint16_t)(rdata[i] << 8); |
| } |
| key_tag += (key_tag >> 16) & 0xFFFF; |
| return (uint16_t)(key_tag & 0xFFFF); |
| } |
| |
| void |
| dns_rdata_txt_to_wire_(dns_towire_state_t *NONNULL txn, const char *NONNULL txt_record, int line) |
| { |
| if (!txn->error) { |
| size_t len = strlen(txt_record); |
| if (txn->p + len + 1 >= txn->lim) { |
| txn->error = ENOBUFS; |
| txn->truncated = true; |
| txn->line = line; |
| return; |
| } |
| if (len > 255) { |
| txn->error = ENAMETOOLONG; |
| txn->line = line; |
| return; |
| } |
| *txn->p++ = (uint8_t)len; |
| memcpy(txn->p, txt_record, len); |
| txn->p += len; |
| } |
| } |
| |
| void |
| dns_rdata_raw_data_to_wire_(dns_towire_state_t *NONNULL txn, const void *NONNULL raw_data, size_t length, int line) |
| { |
| if (!txn->error) { |
| if (txn->p + length > txn->lim) { |
| txn->error = ENOBUFS; |
| txn->truncated = true; |
| txn->line = line; |
| return; |
| } |
| memcpy(txn->p, raw_data, length); |
| txn->p += length; |
| } |
| } |
| |
| void |
| dns_edns0_header_to_wire_(dns_towire_state_t *NONNULL txn, uint16_t mtu, uint8_t xrcode, uint8_t version, bool DO, int line) |
| { |
| if (!txn->error) { |
| if (txn->p + 9 >= txn->lim) { |
| txn->error = ENOBUFS; |
| txn->truncated = true; |
| txn->line = line; |
| return; |
| } |
| *txn->p++ = 0; // root label |
| dns_u16_to_wire(txn, dns_rrtype_opt); |
| dns_u16_to_wire(txn, mtu); |
| *txn->p++ = xrcode; |
| *txn->p++ = version; |
| *txn->p++ = DO ? 1 << 7 : 0; // flags (usb) |
| *txn->p++ = 0; // flags (lsb, mbz) |
| } |
| } |
| |
| void |
| dns_edns0_option_begin_(dns_towire_state_t *NONNULL txn, int line) |
| { |
| if (!txn->error) { |
| if (txn->p + 2 >= txn->lim) { |
| txn->error = ENOBUFS; |
| txn->truncated = true; |
| txn->line = line; |
| return; |
| } |
| if (txn->p_opt != NULL) { |
| txn->error = EINVAL; |
| txn->line = line; |
| return; |
| } |
| txn->p_opt = txn->p; |
| txn->p += 2; |
| } |
| } |
| |
| void |
| dns_edns0_option_end_(dns_towire_state_t *NONNULL txn, int line) |
| { |
| ssize_t opt_length; |
| if (!txn->error) { |
| if (txn->p_opt == NULL) { |
| txn->error = EINVAL; |
| txn->line = line; |
| return; |
| } |
| opt_length = txn->p - txn->p_opt - 2; |
| txn->p_opt[0] = (uint8_t)(opt_length >> 8); |
| txn->p_opt[1] = opt_length & 0xff; |
| txn->p_opt = NULL; |
| } |
| } |
| |
| void |
| dns_sig0_signature_to_wire_(dns_towire_state_t *NONNULL txn, srp_key_t *key, uint16_t key_tag, |
| dns_name_pointer_t *NONNULL signer, const char *NONNULL signer_hostname, |
| const char *NONNULL signer_domain, uint32_t timenow, int line) |
| { |
| size_t siglen = srp_signature_length(key); |
| uint8_t *start, *p_signer, *p_signature, *rrstart = txn->p; |
| |
| // 1 name (root) |
| // 2 type (SIG) |
| // 2 class (255) ANY |
| // 4 TTL (0) |
| // 18 SIG RDATA up to signer name |
| // 2 signer name (always a pointer) |
| // 29 bytes so far |
| // signature data (depends on algorithm, e.g. 64 for ECDSASHA256) |
| // so e.g. 93 bytes total |
| |
| if (!txn->error) { |
| dns_u8_to_wire(txn, 0); // root label |
| dns_u16_to_wire(txn, dns_rrtype_sig); |
| dns_u16_to_wire(txn, dns_qclass_any); // class |
| dns_ttl_to_wire(txn, 0); // SIG RR TTL |
| dns_rdlength_begin(txn); |
| start = txn->p; |
| dns_u16_to_wire(txn, 0); // type = 0 for transaction signature |
| dns_u8_to_wire(txn, srp_key_algorithm(key)); |
| dns_u8_to_wire(txn, 0); // labels field doesn't apply for transaction signature |
| dns_ttl_to_wire(txn, 0); // original ttl doesn't apply |
| // If timenow is <300, it's either just after the epoch, or the caller doesn't know what time it is. |
| if (timenow < 300) { |
| dns_u32_to_wire(txn, 0); // Indicate that we have no clock: set expiry and inception times to zero |
| dns_u32_to_wire(txn, 0); |
| } else { |
| dns_u32_to_wire(txn, timenow + 300); // signature expiration time is five minutes from now |
| dns_u32_to_wire(txn, timenow - 300); // signature inception time, five minutes in the past |
| } |
| dns_u16_to_wire(txn, key_tag); |
| |
| p_signer = txn->p; |
| // We store the name in uncompressed form because that's what we have to sign |
| if (signer_hostname != NULL) { |
| dns_name_to_wire(NULL, txn, signer_hostname); |
| } |
| dns_full_name_to_wire(NULL, txn, signer_domain); |
| // And that means we're going to have to copy the signature back earlier in the packet. |
| p_signature = txn->p; |
| |
| // Sign the message, signature RRDATA (less signature) first. |
| if (!srp_sign(txn->p, siglen, (uint8_t *)txn->message, (size_t)(rrstart - (uint8_t *)txn->message), |
| start, (size_t)(txn->p - start), key)) { |
| txn->error = true; |
| txn->line = __LINE__; |
| } else { |
| // Now that it's signed, back up and store the pointer to the name, because we're trying |
| // to be as compact as possible. |
| txn->p = p_signer; |
| dns_pointer_to_wire(NULL, txn, signer); // Pointer to the owner name the key is attached to |
| // And move the signature earlier in the packet. |
| memmove(txn->p, p_signature, siglen); |
| |
| txn->p += siglen; |
| dns_rdlength_end(txn); |
| } |
| |
| if (txn->error) { |
| txn->outer_line = line; |
| } |
| } |
| } |
| |
| // Local Variables: |
| // mode: C |
| // tab-width: 4 |
| // c-file-style: "bsd" |
| // c-basic-offset: 4 |
| // fill-column: 108 |
| // indent-tabs-mode: nil |
| // End: |