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

Web Crypto API (через crypto.subtle) работает иначе, чем большинство синхронных JavaScript-библиотек: почти все операции являются асинхронными и возвращают Promise. Это напрямую влияет на модель ошибок — вместо привычных throw используются отклонённые промисы с объектами DOMException.

Ошибки в криптографических операциях здесь не случайность, а часть контрактов API: неправильные ключи, неподдерживаемые алгоритмы, неверные параметры или попытка использовать ключ не по назначению всегда приводят к строго определённым исключениям.


Синхронные и асинхронные ошибки

В Web Crypto API важно разделять два класса проблем:

Синхронные ошибки (TypeError)

Возникают до выполнения криптографической операции, ещё на этапе проверки входных данных JavaScript-движком:

  • передан undefined вместо ArrayBuffer
  • некорректная структура параметров
  • отсутствуют обязательные поля
  • передан неподдерживаемый тип данных

Такие ошибки выбрасываются через throw, поэтому их можно поймать только через try/catch:

try {
  crypto.subtle.encrypt("AES-GCM", key, data); // ошибка: неправильный вызов
} catch (e) {
  console.log(e.name); // TypeError
}

Асинхронные ошибки (DOMException через Promise)

Возникают уже внутри криптографического ядра браузера и возвращаются через отклонённый Promise:

crypto.subtle.encrypt(algorithm, key, data)
  .catch(err => {
    console.log(err.name);
  });

Типичные значения err.name:

  • OperationError
  • InvalidAccessError
  • DataError
  • NotSupportedError

Основные типы исключений Web Crypto API

OperationError

Наиболее общий тип ошибки, возникающий при невозможности выполнить криптографическую операцию.

Причины:

  • повреждённые входные данные
  • невозможность расшифровки (например, неверный ключ)
  • нарушение целостности данных (особенно в AES-GCM)

Пример:

await crypto.subtle.decrypt(
  { name: "AES-GCM", iv },
  key,
  ciphertext
);

Если iv или key не совпадают с теми, что использовались при шифровании, будет OperationError.


DataError

Возникает, когда данные имеют неправильный формат или не соответствуют ожиданиям алгоритма.

Частые причины:

  • повреждённый ArrayBuffer
  • неверная длина входных данных
  • некорректные параметры ключей

Пример:

await crypto.subtle.importKey(
  "raw",
  new Uint8Array([1, 2, 3]), // слишком короткий ключ
  { name: "AES-GCM" },
  false,
  ["encrypt"]
);

InvalidAccessError

Возникает при попытке использовать ключ не по назначению.

Например:

  • ключ создан без разрешения encrypt, но используется для шифрования
  • ключ предназначен для HMAC, но применяется в AES
  • попытка экспорта закрытого ключа, если это запрещено

Пример:

await crypto.subtle.encrypt(
  { name: "AES-GCM", iv },
  keyWithoutEncryptUsage,
  data
);

NotSupportedError

Возникает, если:

  • алгоритм не поддерживается браузером
  • комбинация параметров недопустима
  • используется неподдерживаемый режим

Пример:

crypto.subtle.generateKey(
  { name: "AES-XYZ", length: 256 }, // несуществующий алгоритм
  true,
  ["encrypt"]
);

Обработка ошибок через async/await

На практике наиболее читаемый способ работы — использование async/await с try/catch.

async function encryptData(key, data, iv) {
  try {
    const encrypted = await crypto.subtle.encrypt(
      { name: "AES-GCM", iv },
      key,
      data
    );

    return encrypted;
  } catch (err) {
    console.log("Ошибка:", err.name);

    if (err.name === "OperationError") {
      // например, неверный ключ или повреждённые данные
    }

    throw err;
  }
}

Особенность Web Crypto API: почти все ошибки приходят как DOMException, а не стандартные Error. Поэтому важно ориентироваться именно на name, а не на message.


Обработка ошибок через Promise

Альтернативный стиль — классическая цепочка .then().catch():

crypto.subtle.decrypt(algorithm, key, data)
  .then(result => {
    console.log("Успешно расшифровано");
  })
  .catch(err => {
    if (err.name === "OperationError") {
      console.log("Ошибка расшифровки");
    }
  });

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


Ошибки при работе с ключами

Ключи — основной источник проблем в Web Crypto API.

Типичные ситуации:

1. Неправильные usage-флаги

const key = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true,
  ["encrypt"] // нет "decrypt"
);

await crypto.subtle.decrypt(
  { name: "AES-GCM", iv },
  key,
  data
);

Результат: InvalidAccessError


2. Попытка повторного использования ключа в неподдерживаемом контексте

Некоторые ключи нельзя экспортировать или использовать повторно в другом алгоритме.


3. Ошибки импорта ключей

await crypto.subtle.importKey(
  "raw",
  new Uint8Array([]), // пустой ключ
  { name: "HMAC", hash: "SHA-256" },
  false,
  ["sign"]
);

Результат: DataError


Ошибки при шифровании AES-GCM

AES-GCM — один из самых чувствительных алгоритмов в контексте ошибок.

Критические параметры:

  • iv (инициализационный вектор)
  • длина ключа
  • целостность зашифрованного блока
await crypto.subtle.encrypt(
  { name: "AES-GCM", iv: new Uint8Array(12) },
  key,
  data
);

Типовые ошибки:

  • повторное использование iv → логическая ошибка безопасности (не всегда исключение)
  • неправильная длина ivOperationError
  • повреждённый ciphertext → OperationError

Важно: Web Crypto API не всегда явно сигнализирует о криптографически опасных, но технически допустимых операциях (например, повторный IV может не вызвать ошибку).


Особенности DOMException в Web Crypto

Ошибки в Web Crypto API не являются стандартными Error. Они имеют структуру DOMException:

catch (err) {
  console.log(err.name);    // тип ошибки
  console.log(err.message); // описание (не всегда полезно)
  console.log(err.code);    // устаревшее поле, почти не используется
}

Ключевая особенность — стабильность name. Именно на него следует опираться в логике обработки.


Рекомендованный шаблон обработки

В реальных приложениях используется централизованный обработчик:

function handleCryptoError(err) {
  switch (err.name) {
    case "OperationError":
      console.log("Криптографическая операция не выполнена");
      break;

    case "InvalidAccessError":
      console.log("Неправильное использование ключа");
      break;

    case "DataError":
      console.log("Некорректные данные");
      break;

    case "NotSupportedError":
      console.log("Алгоритм не поддерживается");
      break;

    default:
      console.log("Неизвестная ошибка");
  }
}

Использование:

try {
  await crypto.subtle.encrypt(algorithm, key, data);
} catch (err) {
  handleCryptoError(err);
}

Ошибки генерации ключей

При создании ключей через generateKey также возможны сбои:

await crypto.subtle.generateKey(
  { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]) },
  true,
  ["encrypt", "decrypt"]
);

Возможные проблемы:

  • неподдерживаемая длина ключа → NotSupportedError
  • некорректный экспонент → DataError
  • ограничения браузера → OperationError

Ошибки экспорта и импорта ключей

Экспорт:

await crypto.subtle.exportKey("raw", key);

Если ключ не помечен как экспортируемый:

  • InvalidAccessError

Импорт:

await crypto.subtle.importKey(
  "spki",
  keyData,
  algorithm,
  true,
  ["verify"]
);

Ошибки:

  • повреждённый формат ключа → DataError
  • несоответствие алгоритму → InvalidAccessError

Диагностика криптографических ошибок

В отличие от обычного JavaScript, Web Crypto API не даёт детализированных стеков криптографических ошибок.

Поэтому диагностика строится на:

  • проверке входных параметров до вызова API
  • логировании algorithm, key.usages, iv, длины буферов
  • строгой валидации типов данных

Пример защитного слоя:

function assertBuffer(data) {
  if (!(data instanceof ArrayBuffer)) {
    throw new TypeError("Ожидается ArrayBuffer");
  }
}

Частые источники скрытых ошибок

Некоторые проблемы не приводят к исключениям сразу:

  • повторное использование IV в AES-GCM
  • слабая энтропия ключей
  • логическая ошибка в цепочке преобразований данных
  • неверная кодировка (string vs ArrayBuffer)

Такие ошибки проявляются только на этапе дешифрования через OperationError.


Поведение ошибок в разных браузерах

Хотя спецификация стандартизирует DOMException, поведение может немного отличаться:

  • различия в текстах message
  • разная строгость проверки параметров
  • вариативность генерации OperationError vs DataError

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


Стратегия устойчивой обработки

Криптографические операции должны проектироваться с учётом того, что:

  • любая операция может завершиться Promise rejection
  • ошибка может быть вызвана как данными, так и окружением
  • нельзя полагаться на сообщения исключений

Основной принцип обработки — работа через name, а не через текст ошибки, и обязательная проверка данных до вызова crypto.subtle.