Построение произвольных ASN.1-структур вручную

ASN.1 в jsrsasign реализуется через набор классов пространства KJUR.asn1, где каждая структура соответствует отдельному типу ASN.1-узла. Построение произвольных структур сводится к композиции объектов и строгому контролю тегирования, типов и правил DER-энкодинга.

В jsrsasign все элементы ASN.1 наследуются от базового конструктора ASN1Object. Основные строительные блоки:

  • DERInteger
  • DEROctetString
  • DERUTF8String
  • DERNull
  • DERObjectIdentifier
  • DERSequence
  • DERSet
  • DERTaggedObject

Каждый объект формирует собственный DER-фрагмент, который затем может быть вложен в другие структуры.

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

var int = new KJUR.asn1.DERInteger({int: 42});
var str = new KJUR.asn1.DERUTF8String({str: "test"});

var seq = new KJUR.asn1.DERSequence({
  array: [int, str]
});

Каждый объект инкапсулирует:

  • tag (ASN.1 тип)
  • length
  • value (или вложенные элементы)

DER-правила требуют однозначной канонической формы, поэтому порядок байтов и структура фиксированы.

Иерархическое построение сложных структур

ASN.1 почти всегда используется как дерево. В jsrsasign это отражается через вложенные array или set параметры.

Пример вложенности:

var innerSeq = new KJUR.asn1.DERSequence({
  array: [
    new KJUR.asn1.DERInteger({int: 1}),
    new KJUR.asn1.DERUTF8String({str: "inner"})
  ]
});

var outerSeq = new KJUR.asn1.DERSequence({
  array: [
    innerSeq,
    new KJUR.asn1.DERBoolean({bool: true})
  ]
});

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

Контекстные теги и произвольные ASN.1-структуры

Для построения произвольных ASN.1-типов часто требуется использование контекстно-специфических тегов. В jsrsasign это реализуется через DERTaggedObject.

Контекстный тег позволяет:

  • переопределять стандартный тип
  • реализовывать расширения протоколов
  • моделировать структуры X.509, PKCS и кастомные ASN.1-схемы
var tagged = new KJUR.asn1.DERTaggedObject({
  tag: "a0",
  explicit: true,
  obj: new KJUR.asn1.DERUTF8String({str: "context value"})
});

Здесь "a0" обозначает [0] constructed context-specific tag.

Различают два режима:

Explicit tagging

Объект оборачивается в дополнительный слой:

  • внешний тег
  • внутренний полноценный ASN.1 объект

Используется в большинстве X.509 расширений.

Implicit tagging

Тип исходного объекта заменяется:

var implicitTagged = new KJUR.asn1.DERTaggedObject({
  tag: "a1",
  explicit: false,
  obj: new KJUR.asn1.DERInteger({int: 99})
});

В этом случае Integer перестаёт быть INTEGER на уровне ASN.1-разметки и интерпретируется как контекстный тип.

Построение произвольных структур через DERSequence

DERSequence — основной контейнер для большинства ASN.1-структур (сертификаты, ключи, запросы).

Пример сложной произвольной схемы:

var asn1 = new KJUR.asn1.DERSequence({
  array: [
    new KJUR.asn1.DERInteger({int: 10}),
    new KJUR.asn1.DERSequence({
      array: [
        new KJUR.asn1.DERObjectIdentifier({oid: "1.2.840.113549.1.1.5"}),
        new KJUR.asn1.DERNull()
      ]
    }),
    new KJUR.asn1.DEROctetString({hex: "deadbeef"})
  ]
});

Здесь демонстрируется комбинация:

  • INTEGER как идентификатор версии или индекса
  • вложенный SEQUENCE как алгоритмический идентификатор
  • OCTET STRING как бинарные данные

Использование SET и порядок элементов

DERSet отличается от DERSequence тем, что порядок элементов не фиксируется логически, но в DER он всегда сортируется по байтовому представлению.

var set = new KJUR.asn1.DERSet({
  array: [
    new KJUR.asn1.DERUTF8String({str: "b"}),
    new KJUR.asn1.DERUTF8String({str: "a"})
  ]
});

На уровне DER результат будет отсортирован, несмотря на порядок добавления.

Работа с OID и алгоритмическими идентификаторами

OID является ключевым элементом большинства ASN.1-структур криптографии:

var oid = new KJUR.asn1.DERObjectIdentifier({
  oid: "2.5.4.3"
});

OID часто комбинируется с NULL:

var algId = new KJUR.asn1.DERSequence({
  array: [
    new KJUR.asn1.DERObjectIdentifier({oid: "1.2.840.113549.1.1.1"}),
    new KJUR.asn1.DERNull()
  ]
});

Это классический формат идентификатора алгоритма RSA.

Ручное моделирование X.509-подобной структуры

Произвольные ASN.1-структуры часто используются для имитации частей сертификатов:

var tbs = new KJUR.asn1.DERSequence({
  array: [
    new KJUR.asn1.DERInteger({int: 2}),
    algId,
    new KJUR.asn1.DERUTF8String({str: "subject"})
  ]
});

Дальнейшее вложение позволяет формировать полный сертификатный контейнер.

Работа с бинарными данными

Для низкоуровневых структур используется DEROctetString:

var bin = new KJUR.asn1.DEROctetString({
  hex: "0102030405"
});

Либо передача массива байтов через HEX-представление обеспечивает строгую совместимость с DER.

Вложенные контексты и сложные конструкции

При построении сложных протокольных структур часто требуется комбинировать:

  • SEQUENCE внутри SET
  • контекстные теги внутри SEQUENCE
  • OCTET STRING, содержащий сериализованный ASN.1

Пример:

var complex = new KJUR.asn1.DERSequence({
  array: [
    new KJUR.asn1.DERTaggedObject({
      tag: "a0",
      explicit: true,
      obj: new KJUR.asn1.DERSequence({
        array: [
          new KJUR.asn1.DERInteger({int: 1}),
          new KJUR.asn1.DERUTF8String({str: "nested"})
        ]
      })
    }),
    new KJUR.asn1.DERSet({
      array: [
        new KJUR.asn1.DERObjectIdentifier({oid: "1.2.3.4"}),
        new KJUR.asn1.DERNull()
      ]
    })
  ]
});

Контроль DER-энкодинга

jsrsasign автоматически применяет DER-кодирование, включая:

  • минимизацию длины integer encoding
  • canonical ordering SET элементов
  • строгую длину length octets
  • отказ от BER-вариантов

Любая произвольная структура проходит сериализацию через .getEncodedHex():

var hex = complex.getEncodedHex();

Этот метод возвращает конечное DER-представление.

Типовые ошибки при ручной сборке ASN.1

При построении произвольных структур часто возникают ошибки:

  • использование DERSequence вместо DERSet в протоколах, где важен порядок сортировки
  • неправильное применение explicit/implicit тегирования
  • несоответствие OID и NULL в AlgorithmIdentifier
  • попытка вложить сырой HEX без OCTET STRING
  • нарушение DER-каноничности (например, нестабильные SET-порядки при ручной сериализации вне jsrsasign)

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

Построение расширяемых ASN.1-шаблонов

Для динамических структур удобно формировать фабрики узлов:

function makeAttr(typeOid, value) {
  return new KJUR.asn1.DERSequence({
    array: [
      new KJUR.asn1.DERObjectIdentifier({oid: typeOid}),
      new KJUR.asn1.DERUTF8String({str: value})
    ]
  });
}

Такая модель позволяет собирать произвольные ASN.1-графы, сохраняя читаемость и управляемость структуры.

Комбинирование произвольных уровней вложенности

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

  • уровень данных
  • уровень атрибутов
  • уровень криптографических идентификаторов
  • уровень контейнеров протокола

jsrsasign корректно обрабатывает такие структуры, рекурсивно сериализуя каждый узел в DER-представление.