Обработка исключений и коды ошибок

В SJCL обработка ошибок построена вокруг собственного набора исключений, расширяющих стандартный механизм Error в JavaScript. Библиотека стремится не возвращать “тихие” некорректные результаты при криптографических операциях, поэтому большинство критических ситуаций приводит к генерации исключений с фиксированными типами и сообщениями.

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

Основные категории:

  • sjcl.exception.invalid — некорректные входные параметры
  • sjcl.exception.corrupt — повреждённые или неверно сформированные данные
  • sjcl.exception.notReady — использование алгоритма до его инициализации
  • sjcl.exception.bug — внутренняя ошибка библиотеки, указывающая на невозможное состояние

Каждый тип наследуется от общего исключения SJCL, что позволяет строить как точечную, так и глобальную обработку.

Общая модель генерации ошибок

Большинство функций SJCL не возвращают коды ошибок в стиле C или Node.js callback-стиль. Вместо этого используется выбрасывание исключений:

sjcl.exception.invalid = function(message) {
  this.toString = function() {
    return "sjcl.exception.invalid: " + message;
  };
  this.message = message;
};
sjcl.exception.invalid.prototype = new Error();

Подобная модель делает ошибки неизбежно явными: выполнение прерывается сразу при нарушении условий криптографической корректности.

Коды ошибок и семантика

Хотя SJCL не использует числовые error codes в классическом смысле, логика классификации ошибок фактически выполняет ту же роль.

invalid — ошибки параметров

Возникают при нарушении контрактов функций:

  • передан пустой ключ
  • неверный размер блока
  • некорректный формат IV
  • неподдерживаемый режим шифрования

Типичный пример:

if (!key || key.length === 0) {
  throw new sjcl.exception.invalid("key cannot be empty");
}

Такие ошибки указывают на неправильное использование API.

corrupt — ошибки данных

Используются при декодировании или расшифровке:

  • повреждённый ciphertext
  • неверный padding
  • несовпадение MAC (в аутентифицированных режимах)
  • некорректный формат сериализованных данных

Пример:

if (!this._isValidMac(data)) {
  throw new sjcl.exception.corrupt("message authentication failed");
}

Эта категория особенно важна для криптографической целостности: данные считаются недоверенными и не должны интерпретироваться.

notReady — ошибки состояния

Возникают при попытке использовать алгоритм до завершения подготовки:

  • ключ не сгенерирован
  • параметры не инициализированы
  • не выполнен key expansion

Пример:

if (!this._keySchedule) {
  throw new sjcl.exception.notReady("key schedule not initialized");
}

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

bug — внутренние ошибки

Используются как сигнал о невозможном состоянии:

  • нарушение инвариантов библиотеки
  • недостижимые ветки кода
  • несогласованность внутренних структур

Пример:

throw new sjcl.exception.bug("unexpected state in bitArray processing");

Появление таких ошибок рассматривается как дефект реализации, а не неправильное использование API.

Обработка исключений в прикладном коде

SJCL не скрывает ошибки, поэтому стандартный подход строится на try/catch:

try {
  var decrypted = sjcl.decrypt(password, ciphertext);
} catch (e) {
  if (e instanceof sjcl.exception.corrupt) {
    // данные повреждены или ключ неверный
  } else if (e instanceof sjcl.exception.invalid) {
    // ошибка параметров
  } else {
    // непредвиденное состояние
  }
}

Разделение типов позволяет точно определять природу сбоя без анализа строк сообщений.

Проброс и оборачивание ошибок

В сложных системах SJCL-исключения часто оборачиваются в доменные ошибки приложения:

try {
  sjcl.encrypt(key, data);
} catch (e) {
  throw new Error("Encryption pipeline failed: " + e.toString());
}

При этом важно сохранять оригинальный объект ошибки, поскольку именно он содержит точный тип сбоя.

Ошибки при работе с bitArray

Внутренний тип sjcl.bitArray является частым источником исключений:

  • выход за границы массива
  • некорректные операции над словами
  • несовместимость размеров

В таких случаях обычно выбрасывается sjcl.exception.invalid или sjcl.exception.bug, в зависимости от контекста.

Пример:

if (a.length % 32 !== 0) {
  throw new sjcl.exception.invalid("bitArray length must be multiple of 32");
}

Ошибки кодеков и сериализации

SJCL поддерживает различные кодеки (Base64, Hex, JSON). Ошибки здесь возникают при:

  • декодировании повреждённых строк
  • несоответствии формата
  • неверной версии сериализации
throw new sjcl.exception.corrupt("invalid base64 encoding");

Такие ошибки особенно важны при восстановлении ключей и ciphertext из хранилищ.

Практика безопасной обработки

В криптографических системах важно не различать слишком подробно некоторые ошибки на уровне логики приложения. Например, corrupt может означать как повреждение данных, так и неверный пароль, и разделение этих случаев может привести к утечке информации о системе.

Поэтому часто применяется унификация:

try {
  sjcl.decrypt(key, data);
} catch (e) {
  throw new Error("Decryption failed");
}

Поведение при отсутствии обработки

Если исключения SJCL не перехватываются, они полностью прерывают выполнение скрипта. В браузерных средах это приводит к остановке текущего потока выполнения, в Node.js — к аварийному завершению процесса при отсутствии глобального обработчика.

Глобальная обработка:

process.on("uncaughtException", function(e) {
  console.error("Unhandled SJCL error:", e);
});

Особенности проектирования ошибок SJCL

Модель исключений библиотеки ориентирована на несколько принципов:

  • отказ от “молчащих” ошибок
  • строгая типизация сбоев
  • разделение пользовательских и внутренних проблем
  • минимизация неопределённого поведения в криптографии

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