Типизация: работа с TypeScript и файлами деклараций

Библиотека jsrsasign применяется в JavaScript-проектах для криптографических операций: подписи JWT, работа с X.509, RSA/EC ключами, хэширование и проверка сертификатов. При использовании в TypeScript возникает вопрос типизации, поскольку сама библиотека исторически ориентирована на JavaScript и использует динамическую структуру API.

Установка осуществляется стандартно:

npm install jsrsasign

В зависимости от версии и окружения могут встречаться три сценария типизации:

  • наличие встроенных деклараций в пакете
  • подключение внешних типов через DefinitelyTyped
  • использование пользовательских .d.ts файлов

Если типы отсутствуют, TypeScript по умолчанию интерпретирует модуль как any, что приводит к потере контроля типов.


Подключение в TypeScript проектах (ESM и CommonJS)

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

CommonJS вариант

const jsrsasign = require("jsrsasign");

При отсутствии деклараций тип будет any, что ограничивает проверку.

ES Module вариант

import * as jsrsasign from "jsrsasign";

или при наличии корректных типов:

import { KEYUTIL, KJUR } from "jsrsasign";

Выбор варианта зависит от того, как описан модуль в декларациях.


Файлы деклараций и их роль

Файлы .d.ts описывают структуру API библиотеки для TypeScript-компилятора. В контексте jsrsasign они выполняют несколько функций:

  • описание глобальных объектов (KJUR, KEYUTIL, X509)
  • типизация методов криптографии
  • описание структур ключей и сертификатов
  • уточнение возвращаемых значений (строки PEM, ArrayBuffer, hex)

Типичный минимальный пример декларации:

declare module "jsrsasign" {
  export const KEYUTIL: any;
  export const KJUR: any;
}

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

Более развитые декларации включают интерфейсы:

export interface RSAKey {
  n: string;
  e: string;
  d?: string;
}

Типизация основных API: KJUR и KEYUTIL

Основные пространства имён библиотеки:

  • KJUR — криптографические операции
  • KEYUTIL — работа с ключами
  • X509 — сертификаты

Подпись данных

const sig = new KJUR.crypto.Signature({ alg: "SHA256withRSA" });
sig.init(privateKey);
sig.updateString("data");
const signature: string = sig.sign();

В типизированной модели важно фиксировать:

  • алгоритм как union-тип
  • входные данные как string | ArrayBuffer
  • результат как string (hex/base64)

Пример расширенной типизации:

type SignatureAlgorithm =
  | "SHA1withRSA"
  | "SHA256withRSA"
  | "SHA384withRSA";

interface SignatureInstance {
  init(key: string): void;
  updateString(data: string): void;
  sign(): string;
}

Работа с ключами и их типизация

KEYUTIL используется для конвертации ключей:

const rsaKey = KEYUTIL.getKey(pemKey);

Без типов результат обычно считается any. При улучшенной типизации вводится структура:

interface RSAKey {
  isPrivate: boolean;
  isPublic: boolean;
  n: string;
  e: string;
  d?: string;
}

Функции преобразования:

function getKey(pem: string): RSAKey;
function getPEM(key: RSAKey, format: "PKCS1" | "PKCS8"): string;

Проблемы типизации и несоответствия API

Jsrsasign имеет динамическую архитектуру, из-за чего возникают типовые сложности:

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

Типичный пример проблемы:

KJUR.crypto.Signature; // может быть классом или функцией в зависимости от сборки

TypeScript не способен автоматически вывести корректную сигнатуру.


Создание собственных деклараций

При отсутствии официальных типов создаётся файл:

jsrsasign.d.ts

Базовая структура:

declare module "jsrsasign" {
  export namespace KJUR {
    namespace crypto {
      class Signature {
        constructor(params: { alg: string });
        init(key: string): void;
        updateString(data: string): void;
        sign(): string;
      }
    }
  }

  export const KEYUTIL: {
    getKey(pem: string): any;
  };
}

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


Расширение типов (module augmentation)

При наличии частичных типов возможно расширение:

declare module "jsrsasign" {
  interface RSAKey {
    version?: string;
  }
}

Это позволяет добавлять недостающие поля без переписывания всей декларации.


Настройка tsconfig для работы с jsrsasign

Ключевые параметры компилятора:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

Особое значение имеет:

  • skipLibCheck — подавляет ошибки в сторонних .d.ts
  • noImplicitAny — выявляет отсутствие типизации jsrsasign API
  • esModuleInterop — упрощает импорт CommonJS модулей

Работа с X.509 и сертификатами в типизированной среде

Работа с сертификатами включает парсинг PEM:

const x509 = new X509();
x509.readCertPEM(certPem);
const subject: string = x509.getSubjectString();

Типизация для X509:

interface X509Certificate {
  getSubjectString(): string;
  getIssuerString(): string;
  getSerialNumberHex(): string;
}

Обработка криптографических результатов в строгой типизации

Результаты операций jsrsasign часто представлены строками:

  • hex
  • base64
  • PEM

Для повышения безопасности вводятся типы-метки:

type HexString = string;
type Base64String = string;
type PEMString = string;

Пример функции:

function signData(data: string): Base64String;

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

При миграции с JavaScript на TypeScript часто используется промежуточная стратегия:

const KJUR: any = require("jsrsasign");

Дальнейшая эволюция:

  1. any слой
  2. частичные интерфейсы
  3. полная декларация модулей
  4. строгая типизация ключевых операций

Интеграция с криптографическими потоками данных

При работе с бинарными данными типизация усложняется:

function digest(data: string | ArrayBuffer): string;

Расширенный вариант:

type CryptoInput = string | Uint8Array | ArrayBuffer;
type CryptoOutput = string;

Такая модель позволяет унифицировать обработку входных данных в API jsrsasign.


Совместимость типов с внешними JWT-библиотеками

Jsrsasign часто используется совместно с JWT-логикой:

const token = KJUR.jws.JWS.sign(null, header, payload, key);

Типизация:

function sign(
  alg: string | null,
  header: object,
  payload: object,
  key: string
): string;

При строгой модели header и payload уточняются через generics:

interface JWTPayload {
  sub: string;
  exp: number;
}

Ограничения типизации jsrsasign в экосистеме TypeScript

Основные ограничения:

  • отсутствие официальных строго типизированных контрактов
  • использование глобальных пространств имён вместо модулей
  • неоднородные API разных компонентов библиотеки
  • зависимость от формата данных вместо структур

Эти особенности приводят к необходимости внешних деклараций и ручной корректировки типов при масштабных проектах.