LCOV - code coverage report
Current view: top level - tests/unit - test_pii_redact.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 93 93
Test Date: 2026-04-20 19:54:22 Functions: 100.0 % 8 8

            Line data    Source code
       1              : /* SPDX-License-Identifier: GPL-3.0-or-later */
       2              : /* Copyright 2026 Peter Csaszar */
       3              : 
       4              : /**
       5              :  * @file test_pii_redact.c
       6              :  * @brief Unit tests for pii_redact helpers and log-redaction of auth PII.
       7              :  *
       8              :  * Verifies:
       9              :  *   1. redact_phone with various phone shapes (short, long, NULL).
      10              :  *   2. A log-capture test that confirms a send_code log line does NOT
      11              :  *      contain the raw phone_code_hash value.
      12              :  */
      13              : 
      14              : #include "test_helpers.h"
      15              : #include "pii_redact.h"
      16              : #include "logger.h"
      17              : #include "auth_session.h"
      18              : #include "mtproto_session.h"
      19              : #include "transport.h"
      20              : #include "tl_serial.h"
      21              : #include "tl_registry.h"
      22              : #include "mock_socket.h"
      23              : #include "mock_crypto.h"
      24              : 
      25              : #include <string.h>
      26              : #include <stdio.h>
      27              : #include <stdlib.h>
      28              : #include <unistd.h>
      29              : 
      30              : /* ------------------------------------------------------------------ */
      31              : /* redact_phone tests                                                   */
      32              : /* ------------------------------------------------------------------ */
      33              : 
      34            1 : static void test_redact_phone_long(void) {
      35              :     char out[32];
      36            1 :     redact_phone("+15551234567", out, sizeof(out));
      37              :     /* Last 4 digits must be visible */
      38            1 :     ASSERT(strstr(out, "4567") != NULL,
      39              :            "redact_phone: last 4 digits must be present");
      40              :     /* Raw middle digits must NOT be present */
      41            1 :     ASSERT(strstr(out, "555123") == NULL,
      42              :            "redact_phone: middle digits must be masked");
      43              :     /* Must start with '+' */
      44            1 :     ASSERT(out[0] == '+', "redact_phone: must start with '+'");
      45              : }
      46              : 
      47            1 : static void test_redact_phone_short(void) {
      48              :     char out[32];
      49              :     /* A 4-digit bare number — too short to keep last 4 and still mask */
      50            1 :     redact_phone("1234", out, sizeof(out));
      51              :     /* Must not expose digits, output should be the full-mask form */
      52            1 :     ASSERT(strstr(out, "1234") == NULL,
      53              :            "redact_phone: short phone must be fully masked");
      54            1 :     ASSERT(out[0] == '+', "redact_phone: short phone must start with '+'");
      55              : }
      56              : 
      57            1 : static void test_redact_phone_null(void) {
      58              :     char out[32];
      59            1 :     redact_phone(NULL, out, sizeof(out));
      60            1 :     ASSERT(strstr(out, "null") != NULL,
      61              :            "redact_phone: NULL phone must produce '(null)' output");
      62              : }
      63              : 
      64            1 : static void test_redact_phone_empty(void) {
      65              :     char out[32];
      66            1 :     redact_phone("", out, sizeof(out));
      67            1 :     ASSERT(strstr(out, "null") != NULL,
      68              :            "redact_phone: empty phone must produce '(null)' output");
      69              : }
      70              : 
      71            1 : static void test_redact_phone_with_plus(void) {
      72              :     char out[32];
      73            1 :     redact_phone("+447700900123", out, sizeof(out));
      74            1 :     ASSERT(strstr(out, "0123") != NULL,
      75              :            "redact_phone: last 4 of +447700900123 must be 0123");
      76            1 :     ASSERT(strstr(out, "77009") == NULL,
      77              :            "redact_phone: internal digits must not appear");
      78              : }
      79              : 
      80              : /* ------------------------------------------------------------------ */
      81              : /* Log-capture test: send_code must NOT log the raw hash               */
      82              : /* ------------------------------------------------------------------ */
      83              : 
      84              : /* Reuse the fake-response builder from test_auth_session.c */
      85            1 : static void build_fake_encrypted_response_pr(const uint8_t *payload, size_t plen,
      86              :                                               uint8_t *out, size_t *out_len) {
      87              :     TlWriter w;
      88            1 :     tl_writer_init(&w);
      89            1 :     uint8_t zeros24[24] = {0};
      90            1 :     tl_write_raw(&w, zeros24, 24);
      91            1 :     uint8_t header[32] = {0};
      92            1 :     uint32_t plen32 = (uint32_t)plen;
      93            1 :     memcpy(header + 28, &plen32, 4);
      94            1 :     tl_write_raw(&w, header, 32);
      95            1 :     tl_write_raw(&w, payload, plen);
      96            1 :     size_t total = w.len;
      97            1 :     size_t payload_start = 24;
      98            1 :     size_t enc_part = total - payload_start;
      99            1 :     if (enc_part % 16 != 0) {
     100            1 :         size_t pad = 16 - (enc_part % 16);
     101            1 :         uint8_t zeros[16] = {0};
     102            1 :         tl_write_raw(&w, zeros, pad);
     103              :     }
     104            1 :     size_t wire_bytes = w.len;
     105            1 :     size_t wire_units = wire_bytes / 4;
     106            1 :     uint8_t *result = (uint8_t *)malloc(1 + wire_bytes);
     107            1 :     result[0] = (uint8_t)wire_units;
     108            1 :     memcpy(result + 1, w.data, wire_bytes);
     109            1 :     *out_len = 1 + wire_bytes;
     110            1 :     memcpy(out, result, *out_len);
     111            1 :     free(result);
     112            1 :     tl_writer_free(&w);
     113            1 : }
     114              : 
     115              : /** The secret hash used in the log-capture test. */
     116              : #define SECRET_HASH "SUPERSECRET_HASH_XYZ_9876"
     117              : 
     118            1 : static void test_send_code_does_not_log_hash(void) {
     119            1 :     mock_socket_reset();
     120            1 :     mock_crypto_reset();
     121              : 
     122              :     /* Build a fake sentCode response containing the secret hash */
     123              :     TlWriter w;
     124            1 :     tl_writer_init(&w);
     125            1 :     tl_write_uint32(&w, CRC_auth_sentCode);
     126            1 :     tl_write_uint32(&w, 0);                          /* flags */
     127            1 :     tl_write_uint32(&w, CRC_auth_sentCodeTypeApp);
     128            1 :     tl_write_int32(&w, 5);                           /* length */
     129            1 :     tl_write_string(&w, SECRET_HASH);
     130              :     uint8_t payload[512];
     131            1 :     size_t plen = w.len < sizeof(payload) ? w.len : sizeof(payload);
     132            1 :     memcpy(payload, w.data, plen);
     133            1 :     tl_writer_free(&w);
     134              : 
     135              :     uint8_t resp_buf[1024];
     136            1 :     size_t resp_len = 0;
     137            1 :     build_fake_encrypted_response_pr(payload, plen, resp_buf, &resp_len);
     138            1 :     mock_socket_set_response(resp_buf, resp_len);
     139              : 
     140              :     /* Direct logging to a temp file so we can inspect it */
     141            1 :     const char *log_path = "/tmp/tg-cli-pii-test.log";
     142            1 :     unlink(log_path);
     143            1 :     logger_init(log_path, LOG_DEBUG);
     144              : 
     145              :     MtProtoSession s;
     146            1 :     mtproto_session_init(&s);
     147            1 :     s.session_id = 0;
     148            1 :     uint8_t fake_key[256] = {0};
     149            1 :     mtproto_session_set_auth_key(&s, fake_key);
     150            1 :     mtproto_session_set_salt(&s, 0x1122334455667788ULL);
     151              : 
     152              :     Transport t;
     153            1 :     transport_init(&t);
     154            1 :     t.fd = 42; t.connected = 1; t.dc_id = 1;
     155              : 
     156              :     ApiConfig cfg;
     157            1 :     api_config_init(&cfg);
     158            1 :     cfg.api_id = 12345; cfg.api_hash = "deadbeef";
     159              : 
     160              :     AuthSentCode result;
     161            1 :     memset(&result, 0, sizeof(result));
     162            1 :     int rc = auth_send_code(&cfg, &s, &t, "+15551234567", &result, NULL);
     163            1 :     logger_close();
     164              : 
     165            1 :     ASSERT(rc == 0, "log-capture: send_code must succeed");
     166              : 
     167              :     /* Read log and verify the secret hash is absent */
     168            1 :     FILE *f = fopen(log_path, "r");
     169            1 :     ASSERT(f != NULL, "log-capture: log file must exist");
     170            1 :     if (!f) return;
     171              : 
     172              :     char line[512];
     173            1 :     int found = 0;
     174            3 :     while (fgets(line, sizeof(line), f)) {
     175            2 :         if (strstr(line, SECRET_HASH)) { found = 1; break; }
     176              :     }
     177            1 :     fclose(f);
     178            1 :     unlink(log_path);
     179              : 
     180            1 :     ASSERT(found == 0,
     181              :            "log-capture: phone_code_hash must NOT appear in log output");
     182              : }
     183              : 
     184              : /* ------------------------------------------------------------------ */
     185              : /* Entry point called from test_runner.c                               */
     186              : /* ------------------------------------------------------------------ */
     187              : 
     188            1 : void run_pii_redact_tests(void) {
     189            1 :     RUN_TEST(test_redact_phone_long);
     190            1 :     RUN_TEST(test_redact_phone_short);
     191            1 :     RUN_TEST(test_redact_phone_null);
     192            1 :     RUN_TEST(test_redact_phone_empty);
     193            1 :     RUN_TEST(test_redact_phone_with_plus);
     194            1 :     RUN_TEST(test_send_code_does_not_log_hash);
     195            1 : }
        

Generated by: LCOV version 2.0-1