Тестирование невалидных и повреждённых токенов

При работе с защищёнными структурами данных в формате Iron основная уязвимая зона — процесс восстановления исходного объекта из сериализованной и зашифрованной строки. Любое отклонение от ожидаемого формата приводит к ошибкам десериализации или, в худшем случае, к некорректной интерпретации данных.

Невалидные токены в контексте Iron можно разделить на несколько категорий:

  • полностью повреждённые строки (обрезанные или дополненные случайными символами)
  • токены с изменённой криптографической подписью
  • токены, зашифрованные другим ключом
  • токены с некорректной структурой JSON после расшифровки
  • токены с нарушением кодировки (UTF-8 / Base64URL)
  • устаревшие или повторно использованные токены при наличии политики одноразовости

Каждый тип ошибки должен проверяться отдельно, поскольку механизм unseal в Iron реагирует на них по-разному.


Базовый механизм проверки в Iron

В библиотеке @hapi/iron основной операцией является восстановление данных:

import Iron from '@hapi/iron';

const password = 'strong-password';
const sealed = await Iron.seal({ user: 'admin', role: 'root' }, password, Iron.defaults);

const unsealed = await Iron.unseal(sealed, password, Iron.defaults);

Любое отклонение от корректного значения sealed должно приводить к исключению. Это поведение используется как основа для тестирования.


Проверка повреждённой строки токена

Наиболее частый сценарий — обрезанный или изменённый токен, например при ошибке хранения или передачи через URL.

import Iron from '@hapi/iron';

const password = 'strong-password';

async function testTruncatedToken() {
  const valid = await Iron.seal({ id: 1 }, password, Iron.defaults);

  const corrupted = valid.slice(0, valid.length - 10);

  try {
    await Iron.unseal(corrupted, password, Iron.defaults);
    throw new Error('Токен не должен быть валидным');
  } catch (err) {
    console.log('Ошибка корректно обработана:', err.message);
  }
}

Ожидаемое поведение — исключение на этапе проверки подписи или структуры.


Тестирование изменения символов внутри токена

Манипуляции с содержимым токена позволяют проверить устойчивость MAC-подписи.

async function testModifiedToken() {
  const valid = await Iron.seal({ id: 42 }, password, Iron.defaults);

  const modified = valid.replace(/a/g, 'b');

  try {
    await Iron.unseal(modified, password, Iron.defaults);
  } catch (err) {
    console.log('Обнаружено изменение токена');
  }
}

Даже минимальная модификация должна нарушать целостность, поскольку подпись пересчитывается при проверке.


Использование неправильного ключа

Один из критических сценариев — попытка расшифровки корректного токена другим секретом.

async function testWrongPassword() {
  const token = await Iron.seal({ id: 100 }, 'correct-password', Iron.defaults);

  try {
    await Iron.unseal(token, 'wrong-password', Iron.defaults);
  } catch (err) {
    console.log('Неверный ключ отклонён');
  }
}

Этот тест подтверждает, что данные надёжно привязаны к секрету.


Повреждение структуры Base64URL

Iron использует сериализацию, основанную на безопасном представлении строки. Нарушение кодировки часто возникает при ручной обработке токена.

async function testInvalidEncoding() {
  const token = await Iron.seal({ role: 'user' }, password, Iron.defaults);

  const broken = token.replace(/[-_]/g, '+');

  try {
    await Iron.unseal(broken, password, Iron.defaults);
  } catch (err) {
    console.log('Ошибка кодировки обработана');
  }
}

Подобные изменения часто встречаются при ошибках проксирования или неправильной обработке URL-safe Base64.


Тестирование частично валидных токенов

Иногда токен сохраняет структуру, но теряет часть данных внутри зашифрованного payload.

async function testPartiallyCorruptedPayload() {
  const token = await Iron.seal({ session: { id: 1, permissions: ['read', 'write'] } }, password, Iron.defaults);

  const parsed = token.split('*');
  parsed[parsed.length - 1] = parsed[parsed.length - 1].slice(5);
  const corrupted = parsed.join('*');

  try {
    await Iron.unseal(corrupted, password, Iron.defaults);
  } catch (err) {
    console.log('Повреждение payload обнаружено');
  }
}

Такие тесты полезны для проверки устойчивости парсера и криптографического слоя.


Проверка повторного использования токена

Если система предполагает одноразовые токены, необходимо тестировать сценарии replay-атаки.

const usedTokens = new Set();

async function verifyOnce(token) {
  if (usedTokens.has(token)) {
    throw new Error('Повторное использование токена запрещено');
  }

  const data = await Iron.unseal(token, password, Iron.defaults);
  usedTokens.add(token);

  return data;
}

Тестирование включает повторную передачу одного и того же значения.


Тестирование токенов с неверной структурой Iron

Iron-токен имеет строгую внутреннюю структуру. Любое нарушение количества сегментов приводит к немедленной ошибке.

async function testInvalidStructure() {
  const invalid = 'abc.def.ghi';

  try {
    await Iron.unseal(invalid, password, Iron.defaults);
  } catch (err) {
    console.log('Структура токена некорректна');
  }
}

Такие тесты выявляют ошибки на уровне парсинга до криптографической проверки.


Генерация массовых негативных тестов

Для автоматизации проверки устойчивости системы применяются генераторы повреждённых токенов.

function mutate(token) {
  const mutations = [
    t => t + 'a',
    t => t.slice(1),
    t => t.replace(/\./g, ''),
    t => t.split('').reverse().join(''),
  ];

  return mutations.map(fn => fn(token));
}

async function runFuzzTest() {
  const base = await Iron.seal({ test: true }, password, Iron.defaults);

  const cases = mutate(base);

  for (const c of cases) {
    try {
      await Iron.unseal(c, password, Iron.defaults);
    } catch (e) {
      console.log('Ожидаемая ошибка:', e.message);
    }
  }
}

Такой подход позволяет выявить нестандартные сценарии повреждения данных.


Анализ поведения ошибок при unseal

Важно различать типы исключений:

  • криптографическая ошибка (MAC mismatch)
  • ошибка декодирования
  • ошибка структуры
  • ошибка JSON-десериализации

Разделение этих случаев позволяет точнее диагностировать источник проблемы в продакшене.


Тестирование граничных случаев длины токена

Некорректные длины строки часто возникают при обрезке HTTP-заголовков или ограничениях cookie.

async function testBoundaryLengths() {
  const token = await Iron.seal({ data: 'x'.repeat(1000) }, password, Iron.defaults);

  const shortened = token.substring(0, 50);

  try {
    await Iron.unseal(shortened, password, Iron.defaults);
  } catch (err) {
    console.log('Граничная длина обработана корректно');
  }
}

Поведение при случайных бинарных вставках

Иногда токен повреждается при неверной обработке как бинарного буфера.

async function testBinaryCorruption() {
  const token = await Iron.seal({ id: 7 }, password, Iron.defaults);

  const corrupted = Buffer.from(token);
  corrupted[5] = 0x00;

  try {
    await Iron.unseal(corrupted.toString(), password, Iron.defaults);
  } catch (err) {
    console.log('Бинарное повреждение выявлено');
  }
}

Итоговая модель тестирования устойчивости

Полноценная стратегия тестирования невалидных токенов в Iron строится на сочетании:

  • криптографических негативных тестов (подпись, ключ)
  • структурных проверок (формат, сегменты)
  • тестов кодировки (Base64URL)
  • сценариев повреждения при передаче (обрезка, замена)
  • fuzz-тестирования

Такая комбинация позволяет обеспечить предсказуемое поведение unseal при любых нарушениях входных данных и исключить возможность частичного восстановления или ложной интерпретации данных.