/** * @brief Perform a case-sensitive comparison of two managed strings. * @param a the first managed string to be compared. * @param b the second managed string to be compared. * @result -1 if a < b, 1 if b < a, or 0 if the two memory blocks are equal. */ int_t st_cmp_cs_eq(stringer_t *a, stringer_t *b) { bool_t ae, be; int_t result = 0; uchr_t *aptr, *bptr; size_t alen, blen, check; // Setup. ae = st_empty_out(a, &aptr, &alen); be = st_empty_out(b, &bptr, &blen); // Empty string checks. if (ae && be) return 0; else if (ae) return -1; else if (be) return 1; // Calculate how many bytes to compare. check = (alen <= blen ? alen : blen); // Break on the first non matching byte. for (size_t i = 0; result == 0 && i < check; i++) { if (*aptr < *bptr) result = -1; else if (*aptr > *bptr) result = 1; aptr++; bptr++; } // If the strings match, then the longer string is greater if (result == 0 && alen < blen) result = -1; else if (result == 0 && alen > blen) result = 1; return result; }
/** * @brief Perform a case-insensitive check to see if one string starts with another. * @param s the managed string to have its beginning characters examined. * @param starts the managed string to be compared against the beginning of s. * @return -1 if s < starts, 1 if starts < s or 0 if s begins with starts. */ int_t st_cmp_ci_starts(stringer_t *s, stringer_t *starts) { bool_t se, starte; int_t result = 0; uchr_t *sptr, *startptr; size_t slen, startlen, check; // Setup. se = st_empty_out(s, &sptr, &slen); starte = st_empty_out(starts, &startptr, &startlen); // Empty string checks. if (se && starte) return 0; else if (se) return -1; else if (starte) return 1; // Calculate how many bytes to compare. check = (slen <= startlen ? slen : startlen); // Break on the first non matching byte. for (size_t i = 0; result == 0 && i < check; i++) { if (lower_chr(*sptr) < lower_chr(*startptr)) result = -1; else if (lower_chr(*sptr) > lower_chr(*startptr)) result = 1; sptr++; startptr++; } // If the string length is equal/greater and result is still set to 0, we have a match. if (result == 0 && slen < startlen) result = -1; return result; }
/** * @brief Search for a character inside of a managed string, and save its location. * @param haystack the managed string to be searched. * @param needle the character to be found in the string. * @param location if not NULL, a pointer to store the index of needle if found, or 0 on no match. * @return true if the specified character was found or false otherwise. */ bool_t st_search_chr(stringer_t *haystack, chr_t needle, size_t *location) { uchr_t *h; size_t hlen; if (st_empty_out(haystack, &h, &hlen)) { log_pedantic("Passed an empty string."); return false; } // If a location was provided for storing the position of needle, store reset it to zero in case needle isn't found. if (location) { *location = 0; } for (size_t i = 0; i < hlen; i++) { if (h[i] == needle) { if (location) { *location = i; } return true; } } return false; }
/** * @brief Check a URL string for validity. * @note This function confirms that the URL consists of only legal characters and that all escaped sequences are also valid. * @param s a managed string containing the URL to be verified. * @return 0 on failure, or the number of valid characters in the URL if the string is valid. */ size_t url_valid_st(stringer_t *s) { uchr_t *p; size_t c = 0, len; bool_t result = true; if (st_empty_out(s, &p, &len)) { return 0; } // Iterates through and counts valid characters. while (result && len) { if (url_valid_chr(*p)) { len--; c++; p++; } // If an invalid character is found check whether its a properly formed escape sequence. else if (len >= 3 && *p == '%' && hex_valid_chr(*(p + 1)) && hex_valid_chr(*(p + 2))) { len -= 3; p += 3; c++; } else { result = false; len = 0; } } return c; }
/** * @brief Count the number of "Received:" lines in a mail message. * @param message a managed string containing the mail message to be scanned. * @return the number of matching lines found, or 0 on failure. */ uint32_t mail_count_received(stringer_t *message) { size_t len; uchr_t *ptr; uint32_t result = 0; if (st_empty_out(message, &ptr, &len) || len <= 9) { log_pedantic("Message is too short to hold any valid received lines."); return result; } // Loop through and check every new line. // LOW: This loop could be improved using the new string interfaces. for (size_t i = 0; i <= (len - 9); i++) { // Make sure we detect the end of a header on messages without headers. if (*ptr == '\n' || i == 0) { // Detect the end of a header. if (*(ptr + 1) == '\r' && *(ptr + 2) == '\n') { i = len; } else if (*(ptr + 1) == '\n') { i = len; } else if (st_cmp_ci_starts(PLACER(ptr + 1, len - i - 1), PLACER("Received:", 9)) == 0) { result++; } } ptr++; } return result; }
/** * @brief Encode a MIME part for a provided block of data (file attachment) with the specified filename. * @note This function will look up the media type based on the supplied filename, and use that media type as a determination * of whether the content is to be encoded as either quoted-printable or base64. * @param data a pointer to a managed string containing the body of the data to be encoded. * @param filename a pointer to a managed string containing the filename of the attachment for which the data was provided. * @param boundary a pointer to a managed string containing the boundary that will be used to separate the individual MIME parts. * @return NULL on failure, or a pointer to a managed string containing the file attachment encoded as a MIME part on success. */ stringer_t * mail_mime_encode_part(stringer_t *data, stringer_t *filename, stringer_t *boundary) { stringer_t *result, *encoded; media_type_t *mtype; chr_t *fstart, *extptr = NULL, *ctype; size_t flen; if (!data) { return NULL; } // First get the extension of the filename so we can look up the media type. if (!st_empty_out(filename, (uchr_t **)&fstart, &flen)) { extptr = fstart + flen + 1; while (extptr >= fstart) { if (*extptr == '.') { break; } extptr--; } if (extptr < fstart) { extptr = NULL; } } mtype = mail_mime_get_media_type (extptr); if (mtype->bin) { encoded = base64_encode(data, NULL); ctype = "base64"; } else { encoded = qp_encode(data); ctype = "quoted-printable"; } if (!encoded) { log_pedantic("Unable to encode MIME part data."); return NULL; } // What we return is: boundary/CRLF, Content-Type/CRLF, Content-Transfer-Encoding/CRLF, Content-Disposition/CRLF, data/CRLF if (!(result = st_merge("nsnnnnnnnsns", "--------------", boundary, "\r\n", "Content-Type: ", mtype->name, ";\r\n", "Content-Transfer-Encoding: ", ctype, "\r\nContent-Disposition: attachment; filename=\"", filename, "\"\r\n\r\n", encoded))) { log_pedantic("Unable to generate MIME part data."); return NULL; } st_free(encoded); return result; }
/** * @brief Search one managed string for an occurrence of another in a case-sensitive manner, and save its location. * @param haystack the managed string to be searched. * @param needle the managed string to be found. * @param location if not NULL, a pointer to store the index of needle if found, or 0 on no match. * @return true if the string is found or false otherwise. */ bool_t st_search_cs(stringer_t *haystack, stringer_t *needle, size_t *location) { uchr_t *h, *n; bool_t result = false; size_t i, j, hlen, nlen; if (st_empty_out(haystack, &h, &hlen) || st_empty_out(needle, &n, &nlen)) { log_pedantic("Passed an empty string."); return false; } else if (nlen > hlen) { return false; } // If a location was provided for storing the position of needle, store reset it to zero in case needle isn't found. if (location) { *location = 0; } // The needle will never be found if it's longer than the haystack. if (nlen > hlen) { return false; } for (i = 0; i <= hlen - nlen && !result; i++) { for (j = 0; j < nlen && (*(h + j) == *(n + j)); j++); // In theory, j won't ever equal nlen if a non-matching character is found. if (j == nlen && location) { result = true; *location = i; } else if (j == nlen) { result = true; } else { h++; } } return result; }
/** * @brief Convert a hex string into a binary data blob. * @note All hex strings should be composed of pairs of two hex characters representing individual bytes. * Invalid hex characters will simply be ignored during processing. * @param h a managed string containing the input hex string to be decoded. * @param output if not NULL, a pointer to a managed string to contain the decoded binary output; if NULL, a new string * will be allocated and returned to the caller. * @return a pointer to a managed string containing the decoded output, or NULL on failure. */ stringer_t * hex_decode_st(stringer_t *h, stringer_t *output) { uint32_t opts = 0; uchr_t *p = NULL, *o, c = 0; size_t w = 0, len = 0, valid; stringer_t *result = NULL; if (output && !st_valid_destination((opts = *((uint32_t *)output)))) { log_pedantic("An output string was supplied but it does not represent a buffer capable of holding the output."); return NULL; } else if (st_empty_out(h, &p, &len) || !(valid = hex_count_st(h))) { log_pedantic("The input block does not appear to hold any data ready for decoding. {%slen = %zu}", p ? "" : "p = NULL / ", len); return NULL; } // Make sure the output buffer is large enough or if output was passed in as NULL we'll attempt the allocation of our own buffer. if ((result = output) && ((st_valid_avail(opts) && st_avail_get(output) < (valid / 2)) || (!st_valid_avail(opts) && st_length_get(output) < (valid / 2)))) { log_pedantic("The output buffer supplied is not large enough to hold the result. {avail = %zu / required = %zu}", st_valid_avail(opts) ? st_avail_get(output) : st_length_get(output), valid / 2); return NULL; } else if (!output && !(result = st_alloc(valid / 2))) { log_pedantic("The output buffer memory allocation request failed. {requested = %zu}", (valid / 2)); return NULL; } // Store the memory address where the output should be written. o = st_data_get(result); // Loop through the input buffer and translate valid characters into a binary octet. for (size_t i = 0; i < len; i++) { if (hex_valid_chr(*p)) { if (!c) { c = *p; } else { *o++ = hex_decode_chr(c, *p); c = 0; w++; } } p++; } // If an output buffer was supplied that is capable of tracking the data length, or a managed string buffer was allocated update the length param. if (!output || st_valid_tracked(opts)) { st_length_set(result, w); } return result; }
/** * @brief Encode a data buffer as a valid URL component. * @param s a managed string containing the data to be encoded. * @return NULL on failure, or a freshly allocated managed string containing the fully-escaped string suitable for use in a URL. */ stringer_t *url_encode(stringer_t *s) { chr_t hex[4]; uchr_t *p; stringer_t *output, *r; size_t len, expected = 0, written = 0; if (st_empty_out(s, &p, &len)) { log_pedantic("An empty string was passed in for encoding."); return NULL; } // Increment through the stringer and count the characters that need to be encoded. for (size_t i = 0; i < len; i++) { if (url_valid_chr(*p)) { expected++; } else { expected += 3; } p++; } // Allocate one byte for printable characters and three bytes for non-printable characters. if (!(output = st_alloc_opts(MANAGED_T | JOINTED | HEAP, expected))) { log_pedantic("Could not allocate a buffer large enough to hold encoded result. {requested = %zu}", expected); return NULL; } // Get setup. p = st_data_get(s); // Increment through the stringer and copy the data into the new stringer. for (size_t i = 0; i < len; i++) { // Escape the invalid characters. if (url_valid_chr(*p)) { if ((r = st_append(output, PLACER(p, 1)))) { output = r; written++; } } else if (snprintf(hex, 4, "%%%02X", *p) == 3 && (r = st_append(output, PLACER(&hex[0], 3)))) { output = r; written += 3; } // We always advance the input pointer. p++; } st_length_set(output, written); return output; }
/** * @brief Count the number of hex characters in a string. * @param s the managed string to be scanned. * @return the number of valid hexadecimal characters found in the string. */ size_t hex_count_st(stringer_t *s) { uchr_t *p; size_t c = 0, len; if (st_empty_out(s, &p, &len)) { return 0; } // Iterates through and counts valid characters. If an invalid character is found the loop is broken. for (size_t i = 0; i < len; i++) { if (hex_valid_chr(*p++)) c++; } return c; }
/** * @brief Convert a block of binary data into a hex string. * @param b a managed string containing the raw data to be encoded. * @param output if not NULL, a pointer to a managed string that will store the encoded output; if NULL, a new managed string will * be allocated and returned to the caller. * @return NULL on failure, or a pointer to a managed string containing the hex-encoded output on success. */ stringer_t * hex_encode_st(stringer_t *b, stringer_t *output) { size_t len = 0; uint32_t opts = 0; uchr_t *p = NULL, *o; stringer_t *result = NULL; if (output && !st_valid_destination((opts = *((uint32_t *)output)))) { log_pedantic("An output string was supplied but it does not represent a buffer capable of holding the output."); return NULL; } else if (st_empty_out(b, &p, &len)) { log_pedantic("The input block does not appear to hold any data ready for encoding. {%slen = %zu}", p ? "" : "p = NULL / ", len); return NULL; } // Make sure the output buffer is large enough or if output was passed in as NULL we'll attempt the allocation of our own buffer. if ((result = output) && ((st_valid_avail(opts) && st_avail_get(output) < (len * 2)) || (!st_valid_avail(opts) && st_length_get(output) < (len * 2)))) { log_pedantic("The output buffer supplied is not large enough to hold the result. {avail = %zu / required = %zu}", st_valid_avail(opts) ? st_avail_get(output) : st_length_get(output), len * 2); return NULL; } else if (!output && !(result = st_alloc(len * 2))) { log_pedantic("The output buffer memory allocation request failed. {requested = %zu}", len * 2); return NULL; } // Store the memory address where the output should be written. o = st_data_get(result); // Loop through the input buffer and write character pairs to the result string data buffer. for (size_t i = 0; i < len; i++) { hex_encode_chr(*p, o); o += 2; p += 1; } // If an output buffer was supplied that is capable of tracking the data length, or a managed string buffer was allocated update the length param. if (!output || st_valid_tracked(opts)) { st_length_set(result, len * 2); } return result; }
/** * @brief Replace all instances of one character in a managed string with another. * @param target the input string which will be transformed by the character replacement. * @param pattern the character to be searched and replaced in the target string. * @param replacement the character to be substituted for the pattern character in the target string. * @return a pointer to the target managed string. */ stringer_t * st_swap(stringer_t *target, uchr_t pattern, uchr_t replacement) { size_t tlen; uchr_t *tptr; if (st_empty_out(target, &tptr, &tlen)) { log_pedantic("Sanity check failed. Passed a NULL pointer."); return target; } log_check(pattern == replacement); // Increment through and replace the pattern. for (size_t i = 0; i < tlen; i++) { if (*tptr == pattern) *tptr = replacement; tptr++; } return target; }
/** * @brief Determine whether a managed string is a properly formatted hex string and return the number found. * @note A valid hex string consists of only hexadecimal characters and whitespace, and the number of hex characters is divisible by two. * @param s the managed string to be tested. * @return 0 on failure, or the number of hexadecimal characters found in the managed string. */ size_t hex_valid_st(stringer_t *s) { uchr_t *p; size_t c = 0, len; bool_t result = true; if (st_empty_out(s, &p, &len)) { return 0; } // Iterates through and counts valid characters. If an invalid character is found the loop is broken. for (size_t i = 0; result && i < len; i++) { if (hex_valid_chr(*p)) c++; else if (*p != ' ' && *p != '\t' && *p != '\r' && *p != '\n') result = false; p++; } // This check ensures the number of valid characters is at least two and evenly divisible by two. if (!result || (c % 2)) { c = 0; } return c; }
/** * @brief Perform QP (quoted-printable) encoding of a string. * @param s a pointer to a managed string containing data to be encoded. * @return a pointer to a managed string containing the QP encoded data, or NULL on failure. */ stringer_t * qp_encode(stringer_t *s) { chr_t hex[4]; uchr_t *p;//, *o; stringer_t *output, *r; size_t len, expected = 0, line = 0; if (st_empty_out(s, &p, &len)) { log_pedantic("An empty string was passed in for encoding."); return NULL; } // Increment through the stringer and count the characters that need to be encoded. for (size_t i = 0; i < len; i++) { if (*p < '!' || *p > '~' || *p == '=' || *p == ' ' || *p == '\r' || *p == '\n' || *p == '\t') { expected += 3; } else { expected++; } p++; } // Include room for the soft line break sequence every seventy six characters. expected += ((expected + QP_LINE_WRAP_LENGTH) / QP_LINE_WRAP_LENGTH) * 3; // Allocate one byte for printable characters and three bytes for non-printable characters. if (!(output = st_alloc_opts(MANAGED_T | JOINTED | HEAP, expected))) { log_pedantic("Could not allocate a buffer large enough to hold encoded result. {requested = %zu}", expected); return NULL; } // Get setup. p = st_data_get(s); //o = st_data_get(output); // Increment through the stringer and copy the data into the new stringer. for (size_t i = 0; i < len; i++) { // Escape the characters matching this boolean while simply copying any other characters we encounter. if (*p < '!' || *p > '~' || *p == '=' || *p == ' ' || *p == '\r' || *p == '\n' || *p == '\t') { // If were within three characters of the limit append a soft line break to the buffer. if (line > (QP_LINE_WRAP_LENGTH - 3) && snprintf(hex, 4, "=\r\n") == 3 && (r = st_append(output, PLACER(&hex[0], 3)))) { output = r; line = 0; } if (snprintf(hex, 4, "=%02X", *p) == 3 && (r = st_append(output, PLACER(&hex[0], 3)))) { output = r; line += 3; } } else { // If were near the line length limit this will append a soft line break before appending the next character. if (line > (QP_LINE_WRAP_LENGTH - 1) && snprintf(hex, 4, "=\r\n") == 3 && (r = st_append(output, PLACER(&hex[0], 3)))) { output = r; line = 0; } if ((r = st_append(output, PLACER(p, 1)))) { output = r; line++; } } // We always advance the input pointer. p++; } return output; }
/** * @brief Perform QP (quoted-printable) decoding of a string. * @param s the managed string containing data to be decoded. * @return a pointer to a managed string containing the 8-bit decoded output, or NULL on failure. */ stringer_t * qp_decode(stringer_t *s) { uchr_t *p, *o; stringer_t *output; size_t len, written = 0; if (st_empty_out(s, &p, &len)) { log_pedantic("An empty string was passed in for decoding."); return NULL; } // Allocate one byte for printable characters and three bytes for non-printable characters. if (!(output = st_alloc(len))) { log_pedantic("Could not allocate a buffer large enough to hold decoded result. {requested = %zu}", len); return NULL; } // Get setup. o = st_data_get(output); #ifdef MAGMA_PEDANTIC // In pedantic mode we perform an extra check to make sure the loop doesn't loop past zero. while (len && len <= st_length_get(s)) { #else while (len) { #endif // Advance past the trigger. if (*p == '=') { len--; p++; // Valid hex pair. if (len >= 2 && hex_valid_chr(*p) && hex_valid_chr(*(p + 1))) { *o++ = hex_decode_chr(*p, *(p + 1)); written++; len -= 2; p += 2; } // Soft line breaks are signaled by a line break following an equal sign. else if (len >= 2 && *p == '\r' && *(p + 1) == '\n') { len -= 2; p += 2; } else if (len >= 1 && *p == '\n') { len--; p++; } // Equal signs which aren't followed by a valid hex pair or a line break are illegal, but if the character is printable // we can let through the original sequence. else if (len >= 1 && ((*p >= '!' && *p <= '<') || (*p >= '>' && *p <= '~'))) { *o++ = '='; *o++ = *p++; written += 2; len--; } // Characters outside the printable range are simply skipped. else if (len >= 1) { len--; p++; } } // Let through any characters found inside this range. else if ((*p >= '!' && *p <= '<') || (*p >= '>' && *p <= '~')) { *o++ = *p++; written++; len--; } // Characters outside the range above should have been encoded. Any that weren't should be skipped. else { len--; p++; } } // We allocated a default string buffer, which means the length is tracked so we need to set the data length. st_length_set(output, written); return output; }
void imap_command_log_safe(stringer_t *line) { uchr_t *stream, *copy; size_t len, loc = 0; int_t i; if (st_empty_out(line, &stream, &len)) { return; } // A command string that doesn't even contain "LOGIN" is inherently "safe". if (!st_search_ci(line, PLACER("LOGIN", 5), &loc) || !loc) { log_info("%.*s", st_length_int(line), st_char_get(line)); return; } // The LOGIN command should have been preceded by a whitespace. if (!chr_whitespace(stream[loc-1])) { log_info("%.*s", st_length_int(line), st_char_get(line)); return; } // There should be should only be ONE more non-whitespace tag before the LOGIN command. for (i = loc-1; i >= 0; i--) { if (!chr_whitespace(stream[i])) { break; } } if (i < 0) { log_info("%.*s", st_length_int(line), st_char_get(line)); return; } while (i >= 0) { if (chr_whitespace(stream[i])) { log_info("%.*s", st_length_int(line), st_char_get(line)); return; } i--; } if (!(copy = mm_dupe(stream, len))) { return; } // Skip past "LOGIN" ... i = loc + 5; // and the trailing spaces. while ((i < len) && chr_whitespace(stream[i])) { i++; } if (i == len) { log_info("%.*s", st_length_int(line), st_char_get(line)); mm_free(copy); return; } // The next parameter is the username. Skip past that as well. while ((i < len) && !chr_whitespace(stream[i])) { i++; } while ((i < len) && chr_whitespace(stream[i])) { i++; } if (i == len) { log_info("%.*s", st_length_int(line), st_char_get(line)); mm_free(copy); return; } for(;i < len; i++) { copy[i] = '*'; } log_info("%.*s", (int)len, copy); mm_free(copy); return; }
/** * @brief Decrypt a block of data using an ECIES private key. * @param key the ECIES private key in the specified format. * @param key_type the encoding type of the ECIES private key (ECIES_PRIVATE_BINARY or ECIES_PRIVATE_HEX). * @param cryptex a pointer to the head of the cryptex object with the encrypted data. * @param length a pointer to a size_t variable which will receive the final length of the unencrypted data. * @return NULL on failure, or a pointer to a memory address containing the decrypted data on success.. */ uchr_t * deprecated_ecies_decrypt(stringer_t *key, ECIES_KEY_TYPE key_type, cryptex_t *cryptex, size_t *length) { HMAC_CTX hmac; size_t key_length; int output_length; EVP_CIPHER_CTX cipher; EC_KEY *user, *ephemeral; uchr_t *kbuf; size_t hexkey_length; unsigned int mac_length = EVP_MAX_MD_SIZE; unsigned char envelope_key[SHA512_DIGEST_LENGTH], iv[EVP_MAX_IV_LENGTH], md[EVP_MAX_MD_SIZE], *block, *output; // Simple sanity check. if (!key || !cryptex || !length) { log_info("Invalid parameters passed in."); return NULL; } else if ((key_type != ECIES_PRIVATE_HEX) && (key_type != ECIES_PRIVATE_BINARY)) { log_info("Invalid ecies private key type specified!"); return NULL; } else if (st_empty_out(key,&kbuf,&hexkey_length)) { log_info("Could not read key data."); return NULL; } // Make sure we are generating enough key material for the symmetric ciphers. else if ((key_length = EVP_CIPHER_key_length_d(EVP_get_cipherbyname_d(OBJ_nid2sn_d(ECIES_CIPHER)))) * 2 > SHA512_DIGEST_LENGTH) { log_info("The key derivation method will not produce enough envelope key material for the chosen ciphers. {envelope = %i / required = %zu}", SHA512_DIGEST_LENGTH / 8, (key_length * 2) / 8); return NULL; } // Convert the user's public key from hex into a full EC_KEY structure. else if (!(user = deprecated_ecies_key_private(key_type, pl_init(kbuf, hexkey_length)))) { log_info("Invalid private key provided."); return NULL; } // Create the ephemeral key used specifically for this block of data. else if (!(ephemeral = deprecated_ecies_key_public(ECIES_PUBLIC_BINARY, pl_init(deprecated_cryptex_envelope_data(cryptex), deprecated_cryptex_envelope_length(cryptex))))) { log_info("An error occurred while trying to recreate the ephemeral key."); EC_KEY_free_d(user); return NULL; } // Use the intersection of the provided keys to generate the envelope data used by the ciphers below. The ecies_key_derivation() function uses // SHA 512 to ensure we have a sufficient amount of envelope key material and that the material created is sufficiently secure. else if (ECDH_compute_key_d(envelope_key, SHA512_DIGEST_LENGTH, EC_KEY_get0_public_key_d(ephemeral), user, deprecated_ecies_envelope_derivation) != SHA512_DIGEST_LENGTH) { log_info("An error occurred while trying to compute the envelope key. {%s}", ssl_error_string(MEMORYBUF(256), 256)); EC_KEY_free_d(ephemeral); EC_KEY_free_d(user); return NULL; } // The envelope key material has been extracted, so we no longer need the user and ephemeral keys. EC_KEY_free_d(ephemeral); EC_KEY_free_d(user); // Use the authenticated hash of the ciphered data to ensure it was not modified after being encrypted. HMAC_CTX_init_d(&hmac); // At the moment we are generating the hash using encrypted data. At some point we may want to validate the original text instead. if (HMAC_Init_ex_d(&hmac, envelope_key + key_length, key_length, EVP_get_digestbyname_d(OBJ_nid2sn_d(ECIES_HMAC)), NULL) != 1 || HMAC_Update_d(&hmac, deprecated_cryptex_body_data(cryptex), deprecated_cryptex_body_length(cryptex)) != 1 || HMAC_Final_d(&hmac, md, &mac_length) != 1) { log_info("Unable to generate the authentication code needed for validation. {%s}", ssl_error_string(MEMORYBUF(256), 256)); HMAC_CTX_cleanup_d(&hmac); return NULL; } HMAC_CTX_cleanup_d(&hmac); // We can use the generated hash to ensure the encrypted data was not altered after being encrypted. if (mac_length != deprecated_cryptex_hmac_length(cryptex) || memcmp(md, deprecated_cryptex_hmac_data(cryptex), mac_length)) { log_info("The authentication code was invalid! The ciphered data has been corrupted!"); return NULL; } // Create a buffer to hold the result. output_length = deprecated_cryptex_body_length(cryptex); if (!(block = output = mm_alloc(output_length + 1))) { log_info("An error occurred while trying to allocate memory for the decrypted data."); return NULL; } // For now we use an empty initialization vector. We also clear out the result buffer just to be on the safe side. memset(iv, 0, EVP_MAX_IV_LENGTH); memset(output, 0, output_length + 1); EVP_CIPHER_CTX_init_d(&cipher); // Decrypt the data using the chosen symmetric cipher. if (EVP_DecryptInit_ex_d(&cipher, EVP_get_cipherbyname_d(OBJ_nid2sn_d(ECIES_CIPHER)), NULL, envelope_key, iv) != 1 || EVP_CIPHER_CTX_set_padding_d(&cipher, 0) != 1 || EVP_DecryptUpdate_d(&cipher, block, &output_length, deprecated_cryptex_body_data(cryptex), deprecated_cryptex_body_length(cryptex)) != 1) { log_info("Unable to decrypt the data using the chosen symmetric cipher. {%s}", ssl_error_string(MEMORYBUF(256), 256)); EVP_CIPHER_CTX_cleanup_d(&cipher); free(output); return NULL; } block += output_length; if ((output_length = deprecated_cryptex_body_length(cryptex) - output_length) != 0) { log_info("The symmetric cipher failed to properly decrypt the correct amount of data!"); EVP_CIPHER_CTX_cleanup_d(&cipher); free(output); return NULL; } if (EVP_DecryptFinal_ex_d(&cipher, block, &output_length) != 1) { log_info("Unable to decrypt the data using the chosen symmetric cipher. {%s}", ssl_error_string(MEMORYBUF(256), 256)); EVP_CIPHER_CTX_cleanup_d(&cipher); free(output); return NULL; } EVP_CIPHER_CTX_cleanup_d(&cipher); *length = deprecated_cryptex_original_length(cryptex); return output; }
/** * @brief Encrypt a block of data using an ECIES public key. * @param key the ECIES public key in the specified format. * @param key_type the encoding type of the ECIES public key (ECIES_PUBLIC_BINARY or ECIES_PUBLIC_HEX). * @param data a pointer to the block of data to be encrypted. * @param length the length, in bytes, of the data to be encrypted. * @return NULL on failure, or a pointer to the header of the cryptex object containing the encrypted data on success.. */ cryptex_t * deprecated_ecies_encrypt(stringer_t *key, ECIES_KEY_TYPE key_type, unsigned char *data, size_t length) { void *body; HMAC_CTX hmac; int body_length; cryptex_t *cryptex; EVP_CIPHER_CTX cipher; unsigned int mac_length; EC_KEY *user, *ephemeral; size_t envelope_length = 0, block_length = 0, key_length = 0, hexkey_length = 0; uchr_t *kbuf; unsigned char envelope_key[SHA512_DIGEST_LENGTH], iv[EVP_MAX_IV_LENGTH], block[EVP_MAX_BLOCK_LENGTH]; // Simple sanity check. if (!key || !data || !length) { log_info("Invalid parameters passed in."); return NULL; } else if ((key_type != ECIES_PUBLIC_HEX) && (key_type != ECIES_PUBLIC_BINARY)) { log_info("Invalid ecies private key type specified!"); return NULL; } else if (st_empty_out(key,&kbuf,&hexkey_length)) { log_info("Could not read key data."); return NULL; } // Make sure we are generating enough key material for the symmetric ciphers. if ((key_length = EVP_CIPHER_key_length_d(EVP_get_cipherbyname_d(OBJ_nid2sn_d(ECIES_CIPHER)))) * 2 > SHA512_DIGEST_LENGTH) { log_info("The key derivation method will not produce enough envelope key material for the chosen ciphers. {envelope = %i / required = %zu}", SHA512_DIGEST_LENGTH / 8, (key_length * 2) / 8); return NULL; } // Convert the user's public key from hex into a full EC_KEY structure. if (!(user = deprecated_ecies_key_public(key_type, pl_init(kbuf, hexkey_length)))) { log_info("Invalid public key provided."); return NULL; } // Create the ephemeral key used specifically for this block of data. else if (!(ephemeral = deprecated_ecies_key_create())) { log_info("An error occurred while trying to generate the ephemeral key."); EC_KEY_free_d(user); return NULL; } // Use the intersection of the provided keys to generate the envelope data used by the ciphers below. The ecies_key_derivation() function uses // SHA 512 to ensure we have a sufficient amount of envelope key material and that the material created is sufficiently secure. else if (ECDH_compute_key_d(envelope_key, SHA512_DIGEST_LENGTH, EC_KEY_get0_public_key_d(user), ephemeral, deprecated_ecies_envelope_derivation) != SHA512_DIGEST_LENGTH) { log_info("An error occurred while trying to compute the envelope key. {%s}", ssl_error_string(MEMORYBUF(256), 256)); EC_KEY_free_d(ephemeral); EC_KEY_free_d(user); return NULL; } // Determine the envelope and block lengths so we can allocate a buffer for the result. else if ((block_length = EVP_CIPHER_block_size_d(EVP_get_cipherbyname_d(OBJ_nid2sn_d(ECIES_CIPHER)))) == 0 || block_length > EVP_MAX_BLOCK_LENGTH || (envelope_length = EC_POINT_point2oct_d(EC_KEY_get0_group_d(ephemeral), EC_KEY_get0_public_key_d(ephemeral), POINT_CONVERSION_COMPRESSED, NULL, 0, NULL)) == 0) { log_info("Invalid block or envelope length. {block = %zu / envelope = %zu}", block_length, envelope_length); EC_KEY_free_d(ephemeral); EC_KEY_free_d(user); return NULL; } // We use a conditional to pad the length if the input buffer is not evenly divisible by the block size. else if (!(cryptex = deprecated_cryptex_alloc(envelope_length, EVP_MD_size_d(EVP_get_digestbyname_d(OBJ_nid2sn_d(ECIES_HMAC))), length, length + (length % block_length ? (block_length - (length % block_length)) : 0)))) { log_info("Unable to allocate a secure_t buffer to hold the encrypted result."); EC_KEY_free_d(ephemeral); EC_KEY_free_d(user); return NULL; } // Store the public key portion of the ephemeral key. else if (EC_POINT_point2oct_d(EC_KEY_get0_group_d(ephemeral), EC_KEY_get0_public_key_d(ephemeral), POINT_CONVERSION_COMPRESSED, deprecated_cryptex_envelope_data(cryptex), envelope_length, NULL) != envelope_length) { log_info("An error occurred while trying to record the public portion of the envelope key. {%s}", ssl_error_string(MEMORYBUF(256), 256)); EC_KEY_free_d(ephemeral); EC_KEY_free_d(user); deprecated_cryptex_free(cryptex); return NULL; } // The envelope key has been stored so we no longer need to keep the keys around. EC_KEY_free_d(ephemeral); EC_KEY_free_d(user); // For now we use an empty initialization vector. // LOW: Develop a more secure method for selecting and storing the initialization vector. memset(iv, 0, EVP_MAX_IV_LENGTH); // Setup the cipher context, the body length, and store a pointer to the body buffer location. EVP_CIPHER_CTX_init_d(&cipher); body = deprecated_cryptex_body_data(cryptex); body_length = deprecated_cryptex_body_length(cryptex); // Initialize the cipher with the envelope key. if (EVP_EncryptInit_ex_d(&cipher, EVP_get_cipherbyname_d(OBJ_nid2sn_d(ECIES_CIPHER)), NULL, envelope_key, iv) != 1 || EVP_CIPHER_CTX_set_padding_d(&cipher, 0) != 1 || EVP_EncryptUpdate_d(&cipher, body, &body_length, data, length - (length % block_length)) != 1) { log_info("An error occurred while trying to secure the data using the chosen symmetric cipher. {%s}", ssl_error_string(MEMORYBUF(256), 256)); EVP_CIPHER_CTX_cleanup_d(&cipher); deprecated_cryptex_free(cryptex); return NULL; } // Check whether all of the data was encrypted. If they don't match up, we either have a partial block remaining, or an error occurred. else if (body_length != length) { // Make sure all that remains is a partial block, and their wasn't an error. if (length - body_length >= block_length) { log_info("Unable to secure the data using the chosen symmetric cipher. {%s}", ssl_error_string(MEMORYBUF(256), 256)); EVP_CIPHER_CTX_cleanup_d(&cipher); deprecated_cryptex_free(cryptex); return NULL; } // Copy the remaining data into our partial block buffer. The memset() call ensures any extra bytes will be zero'ed out. memset(block, 0, EVP_MAX_BLOCK_LENGTH); memcpy(block, data + body_length, length - body_length); // Advance the body pointer to the location of the remaining space, and calculate just how much room is still available. body += body_length; if ((body_length = deprecated_cryptex_body_length(cryptex) - body_length) < 0) { log_info("The symmetric cipher overflowed!"); EVP_CIPHER_CTX_cleanup_d(&cipher); deprecated_cryptex_free(cryptex); return NULL; } // Pass the final partially filled data block into the cipher as a complete block. The padding will be removed during the decryption process. else if (EVP_EncryptUpdate_d(&cipher, body, &body_length, block, block_length) != 1) { log_info("Unable to secure the data using the chosen symmetric cipher. {%s}", ssl_error_string(MEMORYBUF(256), 256)); EVP_CIPHER_CTX_cleanup_d(&cipher); deprecated_cryptex_free(cryptex); return NULL; } } // Advance the pointer, then use pointer arithmetic to calculate how much of the body buffer has been used. The complex logic is needed so that we get // the correct status regardless of whether there was a partial data block. body += body_length; if ((body_length = deprecated_cryptex_body_length(cryptex) - (body - deprecated_cryptex_body_data(cryptex))) < 0) { log_info("The symmetric cipher overflowed!"); EVP_CIPHER_CTX_cleanup_d(&cipher); deprecated_cryptex_free(cryptex); return NULL; } else if (EVP_EncryptFinal_ex_d(&cipher, body, &body_length) != 1) { log_info("Unable to secure the data using the chosen symmetric cipher. {%s}", ssl_error_string(MEMORYBUF(256), 256)); EVP_CIPHER_CTX_cleanup_d(&cipher); deprecated_cryptex_free(cryptex); return NULL; } EVP_CIPHER_CTX_cleanup_d(&cipher); // Generate an authenticated hash which can be used to validate the data during decryption. HMAC_CTX_init_d(&hmac); mac_length = deprecated_cryptex_hmac_length(cryptex); // At the moment we are generating the hash using encrypted data. At some point we may want to validate the original text instead. if (HMAC_Init_ex_d(&hmac, envelope_key + key_length, key_length, EVP_get_digestbyname_d(OBJ_nid2sn_d(ECIES_HMAC)), NULL) != 1 || HMAC_Update_d(&hmac, deprecated_cryptex_body_data(cryptex), deprecated_cryptex_body_length(cryptex)) != 1 || HMAC_Final_d(&hmac, deprecated_cryptex_hmac_data( cryptex), &mac_length) != 1) { log_info("Unable to generate a data authentication code. {%s}", ssl_error_string(MEMORYBUF(256), 256)); HMAC_CTX_cleanup_d(&hmac); deprecated_cryptex_free(cryptex); return NULL; } HMAC_CTX_cleanup_d(&hmac); return cryptex; }
/** * @brief Decode a URL-encoded string into its original representation. * @param s a managed string containing the UR componentL to be decoded. * @return NULL on failure, or a freshly allocated managed string containing the original data represented by the URL-encoded input on success. */ stringer_t *url_decode(stringer_t *s) { uchr_t *p, *o; stringer_t *output; size_t len, written = 0; if (st_empty_out(s, &p, &len)) { log_pedantic("An empty string was passed in for decoding."); return NULL; } // Allocate one byte for printable characters and three bytes for non-printable characters. if (!(output = st_alloc(len))) { log_pedantic("Could not allocate a buffer large enough to hold decoded result. {requested = %zu}", len); return NULL; } // Get setup. o = st_data_get(output); #ifdef MAGMA_PEDANTIC // In pedantic mode we perform an extra check to make sure the loop doesn't loop past zero. while (len && len <= st_length_get(s)) { #else while (len) { #endif // Advance past the trigger. if (*p == '%') { len--; p++; // Valid hex pair. if (len >= 2 && hex_valid_chr(*p) && hex_valid_chr(*(p + 1))) { *o++ = hex_decode_chr(*p, *(p + 1)); written++; len -= 2; p += 2; } // Percent signs that aren't followed by a valid hex pair are invalid, but in the interest of compatibility we'll simply let // those characters through. else if (len >= 1) { *o++ = '%'; *o++ = *p++; written += 2; len--; } } // Characters not prefixed by a percent sign are simply copied into the output buffer. else { *o++ = *p++; written++; len--; } } // We allocated a default string buffer, which means the length is tracked so we need to set the data length. st_length_set(output, written); return output; }
// LOW: Function could use some serious cleanup; NEED TO TEST THIS IN UNIT TESTS int_t st_replace(stringer_t **target, stringer_t *pattern, stringer_t *replacement) { stringer_t *output; uchr_t *tptr, *optr, *rptr, *pptr; size_t hits = 0, tlen, plen, rlen, olen; // replacement can be blank but it can't be null if (!target || st_empty_out(*target, &tptr, &tlen) || st_empty_out(pattern, &pptr, &plen) || (st_empty_out(replacement, &rptr, &rlen) && !st_char_get(replacement))) { log_pedantic("Sanity check failed. Passed a NULL pointer."); return -1; } // Check to make sure the target is big enough to hold the pattern. if (tlen < plen) { // log_pedantic("The target isn't long enough to contain the pattern."); return 0; } // Increment through the entire target and find out how many times the pattern is present. for (size_t i = 0; i <= (tlen - plen); i++) { if (!st_cmp_cs_starts(PLACER(tptr++, tlen - i), pattern)) { hits++; i += plen - 1; tptr += plen - 1; } } // Did we get any hits? Or if the output length would be zero, return. // QUESTION: Should 2nd part of conditional ever be necessary? tlen - (plen * hits) can't be less than zero, // and hits has to be positive. So the expression will always evaluate positive. // QUESTION: Shouldn't we allow the output length to be zero? /*if (!hits || !(olen = tlen - (plen * hits) + (rlen * hits))) { return hits; }*/ if (!hits) { return 0; } olen = tlen - (plen * hits) + (rlen * hits); // If our new string is now empty we truncate the original target and return it. if (!olen) { *((char *)(st_data_get(*target))) = 0; st_length_set(*target, 0); return hits; } // Allocate a new stringer. if (!(output = st_alloc(olen))) { log_pedantic("Could not allocate %zu bytes for the new string.", olen); // QUESTION: -3? return -3; } // Setup. tptr = st_data_get(*target); optr = st_data_get(output); // Increment through the entire target and copy the bytes. for (size_t i = 0; i <= tlen; i++) { if (i <= (tlen - plen) && !st_cmp_cs_starts(PLACER(tptr, tlen - i), pattern)) { i += plen - 1; tptr += plen; for (size_t j = 0; j < rlen; j++) { *optr++ = *(rptr + j); } } else { *optr++ = *tptr++; } } if (st_valid_free(*((uint32_t *)(*target)))) { st_free(*target); } st_length_set(output, olen); *target = output; return hits; }