X.509-сертификаты используются как стандартный способ распространения и валидации публичных ключей в инфраструктуре открытых ключей. В контексте JOSE (JSON Object Signing and Encryption), который включает JWS, JWE и JWT, сертификаты X.509 выполняют функцию доверенного контейнера для публичного ключа и метаданных о владельце ключа.
В библиотеке Jose X.509 применяется в нескольких ключевых сценариях:
x5c в JWT/JWS;Перед использованием в Jose важно понимать, в каком виде сертификат поступает в систему.
Наиболее распространённый формат:
-----BEGIN CERTIFICATE-----
MIID...
-----END CERTIFICATE-----
Используется для передачи через конфигурации, переменные окружения, файлы.
Чистое бинарное представление сертификата X.509. Обычно используется внутри систем и при работе с низкоуровневыми API.
Хотя это не X.509 напрямую, они тесно связаны:
Jose работает с ними через отдельные функции импорта.
Основная функция для работы с сертификатами:
import { importX509 } from 'jose'
import { importX509 } from 'jose'
const pem = `
-----BEGIN CERTIFICATE-----
MIID... (certificate data)
-----END CERTIFICATE-----
`
const publicKey = await importX509(pem, 'RS256')
Здесь:
pem — строка сертификата;RS256 — алгоритм, для которого будет использоваться
ключ.Функция извлекает публичный ключ из X.509 и приводит его к формату
CryptoKey (Web Crypto API).
Во многих случаях проще использовать SPKI:
import { importSPKI } from 'jose'
const spki = `-----BEGIN PUBLIC KEY-----
MIIB...
-----END PUBLIC KEY-----`
const key = await importSPKI(spki, 'ES256')
Различие:
Одним из ключевых механизмов интеграции X.509 в JOSE является
заголовок x5c.
x5c — это массив сертификатов в формате Base64 DER без
PEM-обёртки:
{
"alg": "RS256",
"x5c": [
"MIID...base64cert...",
"MIIC...intermediate..."
]
}
Первый элемент — сертификат подписывающего ключа.
Jose позволяет проверять JWT, извлекая ключ прямо из сертификата.
import { jwtVerify, importX509 } from 'jose'
async function verifyToken(token) {
const { payload, protectedHeader } = await jwtVerify(token, async (header) => {
const cert = Buffer.from(header.x5c[0], 'base64').toString('utf-8')
return await importX509(cert, header.alg)
})
return payload
}
x5c берётся сертификат.X.509 поддерживает цепочки доверия:
В x5c цепочка представляется массивом:
"x5c": [
"leaf-cert",
"intermediate-cert",
"root-cert"
]
Jose не выполняет полноценную валидацию цепочки как PKI-система, но позволяет:
Хотя Jose не является полноценной библиотекой для работы с X.509 метаданными, можно комбинировать его с Web Crypto API.
Пример получения thumbprint:
import { calculateJwkThumbprint } from 'jose'
const thumbprint = await calculateJwkThumbprint(publicKey)
Используется для:
В системах OpenID Connect часто применяется схема:
x5c или
JWKS;Типичная схема:
x5c.importX509.jwtVerify.Критически важный момент: Jose не выполняет проверку доверия к сертификату.
Это означает:
Все эти проверки должны быть реализованы отдельно.
crypto или внешние
библиотеки;notBefore и notAfter;Частая проблема:
Error: failed to parse certificate
Причина:
importX509(cert, 'ES256')
Если сертификат RSA, а указан ES256, возникнет ошибка
несоответствия ключа.
Некоторые JWT содержат только kid:
{
"kid": "abc123"
}
В этом случае требуется JWKS, а не X.509.
Jose полностью поддерживает Web Crypto API, поэтому X.509 работает в обеих средах.
Требуется:
import { webcrypto } from 'crypto'
globalThis.crypto = webcrypto
Поддержка встроена, дополнительных настроек не требуется.
import { jwtVerify, importX509 } from 'jose'
const token = 'eyJ...'
const result = await jwtVerify(token, async (header) => {
const certPem =
'-----BEGIN CERTIFICATE-----\n' +
header.x5c[0] +
'\n-----END CERTIFICATE-----'
return await importX509(certPem, header.alg)
})
console.log(result.payload)
Помимо JWT, X.509 применяется и в JWS:
import { importX509, SignJWT } from 'jose'
Сценарий:
SignJWT.Сравнение подходов:
В современных системах часто используется комбинация:
Импорт X.509 — относительно дорогая операция.
Рекомендуется:
importX509;thumbprint;Пример:
const cache = new Map()
async function getKey(cert, alg) {
const key = cache.get(cert)
if (key) return key
const imported = await importX509(cert, alg)
cache.set(cert, imported)
return imported
}
Эти аспекты требуют внешней инфраструктуры безопасности.