Jose строится вокруг стандартных Web Crypto API и принципа строгого
разделения криптографических операций и представления ключей. Основная
идея заключается в том, что библиотека не реализует собственные
примитивы шифрования, а использует нативные возможности окружения:
crypto.subtle в браузере или crypto.webcrypto
в Node.js. Это делает возможной работу с аппаратными ключами и HSM без
изменения уровня прикладного кода.
Ключевая абстракция — CryptoKey. В отличие от «сырого»
представления ключа (PEM, JWK, DER), объект CryptoKey может
быть:
extractable: false)keyUsages:
sign, verify, encrypt,
decrypt)Jose оперирует этим объектом напрямую, что позволяет строить системы, где приватный ключ физически не покидает защищённую среду.
Пример создания ключа через WebCrypto:
const keyPair = await crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-256",
},
false,
["sign", "verify"]
);
Здесь false означает, что приватный ключ не может быть
экспортирован, что критично для аппаратных сценариев.
Jose предоставляет высокоуровневый API для JWS (JSON Web Signature), скрывая детали криптографии.
import { SignJWT } from 'jose';
const jwt = await new SignJWT({ role: "admin" })
.setProtectedHeader({ alg: "ES256" })
.setIssuedAt()
.setIssuer("auth-service")
.setExpirationTime("2h")
.sign(privateKey);
privateKey здесь может быть:
CryptoKeyJose не делает различий — важен только интерфейс WebCrypto.
Аппаратные ключи отличаются тем, что:
sign выполняются внутри защищённого
модуляТипичный сценарий — CryptoKey с
extractable: false:
{
type: "private",
extractable: false,
algorithm: { name: "ECDSA", namedCurve: "P-256" },
usages: ["sign"]
}
При использовании Jose это означает, что библиотека передаёт данные в
crypto.subtle.sign, а реальное вычисление выполняется в
аппаратном слое.
WebCrypto не предоставляет прямого доступа к HSM, но служит универсальным интерфейсом. Поддержка аппаратных модулей реализуется через провайдеры:
С точки зрения Jose это выглядит одинаково: доступен
CryptoKey, а реализация скрыта.
В серверной среде аппаратные модули чаще всего подключаются через PKCS#11. Node.js WebCrypto напрямую этого не делает, поэтому используется промежуточный слой.
Пример архитектуры:
Jose → WebCrypto → PKCS#11 provider → HSM
Библиотеки-адаптеры:
node-webcrypto-p11pkcs11jsПример псевдокода:
import { WebCrypto } from "node-webcrypto-p11";
const crypto = new WebCrypto({
library: "/usr/lib/softhsm/libsofthsm2.so",
slot: 0,
pin: "1234"
});
После этого Jose может использовать crypto как
стандартный WebCrypto провайдер.
После подключения HSM ключ выглядит как обычный
CryptoKey, но фактически операция выполняется
аппаратно.
import { SignJWT } from "jose";
const token = await new SignJWT({ sub: "user-123" })
.setProtectedHeader({ alg: "RS256" })
.setIssuedAt()
.sign(hsmPrivateKey);
Важный момент: даже при высокой нагрузке приватный ключ не покидает HSM, а Jose работает как тонкий слой оркестрации.
Публичная часть ключа всегда может быть извлечена и распространяется в виде JWK или PEM.
import { jwtVerify } from "jose";
const { payload } = await jwtVerify(token, publicKey);
Публичный ключ может быть получен из:
Jose активно использует формат JWK для обмена ключами.
import { importJWK } from "jose";
const key = await importJWK({
kty: "EC",
crv: "P-256",
x: "...",
y: "..."
}, "ES256");
При использовании HSM импорт обычно выполняется только для публичных ключей. Приватные ключи остаются внутри устройства.
Несмотря на универсальность, WebCrypto накладывает ряд ограничений:
Jose не обходит эти ограничения, а принимает их как часть модели безопасности.
Типичная схема:
Приложение (Jose)
↓
WebCrypto API
↓
HSM / TPM / Secure Enclave
↓
Аппаратная операция подписи
В такой архитектуре:
Использование HSM влияет на латентность:
Jose не кэширует результат подписи, поэтому оптимизация обычно выполняется на уровне архитектуры:
Попытка экспортировать приватный ключ
await crypto.subtle.exportKey("pkcs8", privateKey);
Для аппаратных ключей это невозможно и приводит к ошибке
InvalidAccessError.
Использование неподдерживаемого алгоритма
HSM может поддерживать только RSA или ECDSA, но не EdDSA.
Смешивание провайдеров WebCrypto
Ключ, созданный в одном провайдере, может быть несовместим с другим.
Jose не управляет ключами, не хранит их и не знает о физическом устройстве. Его задача:
Это делает библиотеку совместимой с любыми аппаратными реализациями без изменений кода прикладного уровня.
В браузере аппаратные ключи обычно приходят из:
Пример получения ключа через WebAuthn и последующего использования:
const credential = await navigator.credentials.get({
publicKey: options
});
// credential.response содержит signature, но ключ остаётся в устройстве
Далее Jose может использовать уже извлечённый публичный ключ для проверки.
Хотя WebAuthn не является частью WebCrypto, он часто используется вместе с Jose для сценариев:
В таких системах подпись не выполняется через Jose напрямую, но результат интегрируется в JWT-потоки.
Использование Jose с HSM позволяет строить модель, где:
Это особенно важно в системах: