Перейти к содержимому
ecdsa.com
Go

go-jose

go-jose — реализация JOSE для Go, которую используют такие проекты, как Kubernetes, Vault и Dex: начиная с v4 при разборе обязателен список алгоритмов, проверка подписи и проверка утверждений выполняются двумя отдельными вызовами, а допуск на расхождение часов по умолчанию равен одной минуте.
Лицензия
Apache-2.0
Проверенная версия
4.1.6 ·
Исходный код
github.com/go-jose/go-jose
Документация
pkg.go.dev/github.com/go-jose/go-jose/v4

Совместимость вкратце

JOSE и JWT

Алгоритмы JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

github.com/go-jose/go-jose/v4 (продолжение go-jose от Square) реализует JWS, JWE, JWK и JWT, поэтому встречается везде, где сервисы на Go обмениваются зашифрованными или подписанными токенами. В версии 4.1.6 подпись и проверка работали для семейств HS, RS, PS и ES, а также для EdDSA; имена алгоритмов ES256K, Ed25519 и Ed448 библиотека не распознаёт.

В версии 4 список алгоритмов стал обязательным: jwt.ParseSigned(token, nil) завершается ошибкой no signature algorithms specified, поэтому токен можно разобрать только с алгоритмами, которые выбрали вы. Неподписанные токены разбираются, если в этом списке есть none, но проверку через Claims(key) они не проходят никогда: прочитать такой токен можно только через UnsafeClaimsWithoutVerification, имя которого прямо предупреждает об отсутствии проверки.

Проверка разделена на два шага, и в этом подвох. token.Claims(key, &claims) проверяет подпись и заполняет структуру: он без всякой ошибки вернул утверждения токена, срок действия которого истёк час назад. Срок действия, начало срока действия, издатель и аудитория проверяются только в claims.Validate() со значением jwt.Expected или в ValidateWithLeeway, и даже эту проверку токен без exp проходит, поэтому убедитесь, что поле Expiry заполнено, как это делает пример. Validate допускает расхождение часов jwt.DefaultLeeway, равное одной минуте, что подтвердила проверка.

Ключи типизированы. Токен HS256, подписанный текстом PEM в качестве ключа, не прошёл проверку с *ecdsa.PublicKey (ошибка error in cryptographic primitive), но прошёл, когда те же байты PEM передали как ключ, поэтому храните ключи в виде разобранных ключей Go, а не байтов. ES256 не принимает ключ P-384 и выдаёт expected 256 bit key, got 384 bits instead. Подпись ES256 в кодировке DER отклоняется с той же общей ошибкой error in cryptographic primitive, что и подделка; длину подписи покажет отладчик JWT.

Заголовок и алгоритмы зашифрованных токенов показывает инспектор JWE; для наборов ключей есть проверка JWKS, которая показывает, что публикует поставщик, и конвертер JWK, который преобразует ключи между JWK и PEM.

Факты и проверки, на которых они основаны

Ничего из приведённого ниже не скопировано из документации. 20 октября 2026 г. проверка content/libraries/verify/go-jose/run.sh запустила go-jose 4.1.6 в образе контейнера golang:1.27.3-trixie; содержимое страницы должно совпадать с выводом проверки, а у каждого факта приведены соответствующие строки вывода.

  • Алгоритмы JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

    Подпись и проверка работают для HS, RS, PS и ES на 256/384/512 бит и для EdDSA; ES256K, Ed25519 и Ed448 вызывают ошибку unknown/unsupported algorithm.

    Что записала проверка
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, EdDSA
    evidence jws-algorithms: ES256K: go-jose/go-jose: unknown/unsupported algorithm
    evidence jws-algorithms: Ed25519: go-jose/go-jose: unknown/unsupported algorithm
    evidence jws-algorithms: Ed448: go-jose/go-jose: unknown/unsupported algorithm
  • Кодировка подписи ES256

    raw

    Подписи ES256 — 64 байта в формате raw r‖s.

    Что записала проверка
    fact jws-ecdsa-encoding = raw
  • Подпись в DER внутри JWS

    rejected

    Подпись ES256 в кодировке DER не проходит проверку с ошибкой go-jose/go-jose: error in cryptographic primitive.

    Что записала проверка
    fact jws-der-signature = rejected
    evidence jws-der-signature: go-jose/go-jose: error in cryptographic primitive
  • Список разрешённых алгоритмов

    required

    jwt.ParseSigned требует список разрешённых алгоритмов; с пустым списком возвращается ошибка no signature algorithms specified.

    Что записала проверка
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: ParseSigned with an empty algorithm list: go-jose/go-jose: no signature algorithms specified
  • Неподписанные токены (alg none)

    rejected

    Если в списке есть none, неподписанный токен разбирается, но Claims() завершается ошибкой и с ключом EC, и с секретом HMAC; прочитать такой токен можно только через UnsafeClaimsWithoutVerification.

    Что записала проверка
    fact jwt-alg-none = rejected
    evidence jwt-alg-none: ParseSigned with none in the list: <nil>
    evidence jwt-alg-none: Claims(*ecdsa.PublicKey): go-jose/go-jose: error in cryptographic primitive
    evidence jwt-alg-none: Claims([]uint8): go-jose/go-jose: error in cryptographic primitive
    evidence jwt-alg-none: UnsafeClaimsWithoutVerification: <nil>, sub fact-check
  • Открытый ключ в роли секрета HMAC

    rejected

    Токен HS256, подписанный текстом PEM в качестве ключа, не прошёл проверку с *ecdsa.PublicKey; проверка прошла, только когда в качестве ключа передали сами байты PEM.

    Что записала проверка
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: Claims with the *ecdsa.PublicKey: go-jose/go-jose: error in cryptographic primitive
    evidence jwt-hmac-public-key: Claims with the PEM bytes as key: <nil>
  • Сверка кривой с alg

    enforced

    При подписи ES256 ключом P-384 возникает ошибка expected 256 bit key, got 384 bits instead.

    Что записала проверка
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: go-jose/go-jose: expected 256 bit key, got 384 bits instead
  • Проверка утверждений при декодировании

    no

    Claims() без ошибки вернул утверждения токена, срок действия которого истёк час назад; время и издатель проверяются только в Validate.

    Что записала проверка
    fact jwt-claims-validated-on-decode = no
    evidence jwt-claims-validated-on-decode: Claims() on a token expired an hour ago: <nil>
  • Допустимое расхождение часов по умолчанию

    60s

    Validate допускает расхождение часов jwt.DefaultLeeway, равное одной минуте: токен, срок действия которого истёк 57 секунд назад, прошёл проверку, а просроченный на 63 секунды — нет.

    Что записала проверка
    fact jwt-clock-skew = 60s
    evidence jwt-clock-skew: jwt.DefaultLeeway = 1m0s
  • Обязательное утверждение о сроке действия

    no

    Токен без exp прошёл Validate; проверяйте сами, что поле Expiry заполнено.

    Что записала проверка
    fact jwt-exp-required = no

Использование

Подпишите зарегистрированные утверждения алгоритмом ES256, разберите токен со списком алгоритмов, проверьте подпись, а затем отдельно проверьте утверждения.
package main

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"fmt"
	"time"

	jose "github.com/go-jose/go-jose/v4"
	"github.com/go-jose/go-jose/v4/jwt"
)

func main() {
	key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	signer, _ := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: key}, (&jose.SignerOptions{}).WithType("JWT"))
	raw, _ := jwt.Signed(signer).Claims(jwt.Claims{Subject: "user-42", Issuer: "https://auth.example.com", Expiry: jwt.NewNumericDate(time.Now().Add(10 * time.Minute))}).Serialize()

	// v4 requires the allowed algorithms when parsing.
	token, _ := jwt.ParseSigned(raw, []jose.SignatureAlgorithm{jose.ES256})
	claims := jwt.Claims{}
	sigErr := token.Claims(&key.PublicKey, &claims) // checks the signature only
	// Validate checks exp, nbf and iss (with jwt.DefaultLeeway of one minute).
	valErr := claims.Validate(jwt.Expected{Issuer: "https://auth.example.com", Time: time.Now()})
	fmt.Println("valid:", sigErr == nil && valErr == nil && claims.Expiry != nil, "sub:", claims.Subject)
}

Тестовый стенд для фрагментов кода запускает каждый пример без изменений в образе контейнера golang:1.27.3-trixie, и запуск завершается ошибкой, если вывод не совпадает.

Команда
GOFLAGS=-mod=mod go run .
Вывод должен совпадать (регулярное выражение)
valid: true sub: user-42

Библиотеки