Skip to content

Сертификаты X.509 ​

sm-crypto-wasm/x509 предоставляет только чтение сертификатов с китайскими алгоритмами и проверку подписи одного сертификата:

  • разбор сертификатов в DER / PEM / base64 / hex;
  • чтение версии, серийного номера, издателя/субъекта, срока действия, открытого ключа и расширений;
  • проверка подписи одного сертификата открытым ключом издателя;
  • вычисление отпечатков SM3 / SHA-256.

Библиотека не реализует проверку цепочки, политик, ограничений имён, а также загрузку CRL / OCSP / AIA. Для проверки цепочки проходите по связям issuer / authorityKeyIdentifier и вызывайте verifySignature() самостоятельно.

Разбор ​

ts
import { parseCertificate, parseCertificates } from 'sm-crypto-wasm/x509';

const cert = parseCertificate(pem);

cert.version;              // 1 | 2 | 3
cert.serialNumber;         // bigint
cert.serialNumberHex;      // '1003'
cert.issuer.toString();    // 'C=CN,O=Example,CN=Example CA'
cert.subject.commonName;   // 'server.example.test'
cert.validity.notBefore;   // Date (точность до секунды)
cert.publicKeyAlgorithm;   // 'SM2' | 'RSA' | 'UNKNOWN'
cert.supported;            // может ли библиотека использовать этот ключ
cert.subjectPublicKey;     // открытый ключ; SM2 — 65 байт 04 || X || Y

// PEM-набор или подряд идущие DER
const chain = parseCertificates(bundle);

Расширения ​

Распространённые расширения доступны как поля; неизвестные сохраняются с OID, флагом critical и исходным значением:

ts
cert.basicConstraints;      // { ca: true, pathLen: 2 }
cert.keyUsage;              // { digitalSignature, keyCertSign, ... }
cert.subjectAltNames;       // [{ type: 'dns' | 'ip' | 'email' | 'uri', value }]
cert.extendedKeyUsage;      // ['1.3.6.1.5.5.7.3.1']
cert.subjectKeyIdentifier;  // Uint8Array
cert.authorityKeyIdentifier;// { keyIdentifier?, authorityCertSerialNumber? }

cert.getExtension('1.2.3.4'); // Extension | undefined

Срок действия и подписи ​

ts
cert.checkValidity();
cert.checkValidity(new Date('2030-01-01T00:00:00Z'));

const ok = cert.verifySignature(caCertificate);

verifySignature() принимает три формы издателя:

  • Certificate, разобранный библиотекой;
  • байты сертификата DER / PEM;
  • открытый ключ (64 или 65 байт).

Идентификатор пользователя SM2:

  • сертификаты GM/T используют идентификатор по умолчанию 1234567812345678;
  • X.509-путь OpenSSL по умолчанию использует пустой идентификатор;
  • если userID не указан, сначала проверяется идентификатор по умолчанию, затем пустой:
ts
cert.verifySignature(ca, { userID: new Uint8Array(0) });

Выпуск ​

ts
import { sm2, sm3 } from 'sm-crypto-wasm';
import { createSelfSignedCertificate, issueCertificate, parseDistinguishedName } from 'sm-crypto-wasm/x509';

await sm2.ensureLoaded();
await sm3.ensureLoaded();                 // SKI по умолчанию использует SM3

const caKeys = await sm2.generateKeyPair();
const ca = createSelfSignedCertificate({
  subject: parseDistinguishedName('C=CN,O=Example,CN=Example CA'),
  privateKey: caKeys.privateKeyPem,
  notAfter: new Date('2035-01-01T00:00:00Z'),
  basicConstraints: { ca: true, pathLen: 1 },
  keyUsage: ['keyCertSign', 'cRLSign'],
});

const leafKeys = await sm2.generateKeyPair();
const leaf = issueCertificate({
  subject: { commonName: 'server.example.test' },
  subjectPublicKey: leafKeys.publicKeyPem, // обязательно при выпуске через CA
  notAfter: new Date('2030-01-01T00:00:00Z'),
  basicConstraints: { ca: false },
  keyUsage: ['digitalSignature', 'keyEncipherment'],
  extendedKeyUsage: ['1.3.6.1.5.5.7.3.1'],
  subjectAltNames: [{ type: 'dns', value: 'server.example.test' }],
  signer: { privateKey: caKeys.privateKeyPem, certificate: ca.certificate },
});

Ограничения:

  • сертификаты всегда v3;
  • ca=false нельзя сочетать с keyCertSign; pathLen только для CA;
  • не-CA сертификат (basicConstraints.ca !== true) не может быть издателем;
  • серийный номер должен быть положительным; по умолчанию — 16 случайных байт со сброшенным старшим битом;
  • DNS/email/URI в SAN должны быть ASCII; IP должен быть корректным IPv4/IPv6.

Идентификатор пользователя SM2:

  • по умолчанию пустой (как в X.509-пути OpenSSL, поэтому openssl verify работает);
  • для идентификатора GM/T передайте userID: DEFAULT_SM2_USER_ID.
ts
import { DEFAULT_SM2_USER_ID } from 'sm-crypto-wasm/x509';

createSelfSignedCertificate({ ..., userID: DEFAULT_SM2_USER_ID });

SKI:

  • по умолчанию первые 20 байт SM3(publicKey);
  • subjectKeyIdentifier: { hash: 'sha1' } соответствует алгоритму OpenSSL;
  • AKI при самоподписи равен SKI, при выпуске через CA берётся SKI сертификата издателя.

Библиотека не генерирует и не хранит закрытые ключи.

Отпечатки ​

ts
cert.fingerprint();          // SM3 (hex, 32 байта)
cert.fingerprint('sha256');  // SHA-256 (встроенная реализация на JS)

Для SM3 нужно заранее загрузить wasm-модуль SM3:

ts
import { sm3 } from 'sm-crypto-wasm';
await sm3.ensureLoaded();

Ошибки ​

Структурные проблемы (обрыв, лишние байты, некорректный DER) вызывают CertificateError:

ts
import { CertificateError } from 'sm-crypto-wasm/x509';

try {
  parseCertificate(input);
} catch (e) {
  if (e instanceof CertificateError) { /* не сертификат / плохая структура */ }
}

Неизвестные расширения, алгоритмы, типы атрибутов Name и сертификаты RSA не вызывают ошибок:

  • неизвестное содержимое сохраняется как исходные байты;
  • сертификаты RSA разбираются, но дают supported === false, а verifySignature() возвращает false.

Область ​

ВозможностьСтатус
Разбор DER / PEM / base64 / hex✅
KeyUsage / BasicConstraints / SAN / SKI / AKI / EKU✅
Проверка подписи SM2 одного сертификата (GM/T + OpenSSL id)✅
Отпечатки SM3 / SHA-256✅
Проверка цепочки сертификатов❌
CRL / OCSP / AIA❌

Лицензия Apache-2.0