Pular para o conteúdo
ecdsa.com
Go

go-jose

O go-jose, a implementação JOSE em Go usada por projetos como Kubernetes, Vault e Dex: desde a v4, listas de algoritmos são obrigatórias no parsing, a verificação da assinatura e a validação das claims são duas chamadas separadas, e a tolerância padrão é de um minuto.
Licença
Apache-2.0
Versão verificada
4.1.6 ·
Código-fonte
github.com/go-jose/go-jose
Documentação
pkg.go.dev/github.com/go-jose/go-jose/v4

Visão geral da compatibilidade

JOSE e JWT

Algoritmos JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

github.com/go-jose/go-jose/v4 (a continuação do go-jose da Square) implementa JWS, JWE, JWK e JWT, e por isso aparece onde quer que serviços Go troquem tokens criptografados ou assinados. A versão 4.1.6 assinou e verificou as famílias HS, RS, PS e ES e o EdDSA; ES256K, Ed25519 e Ed448 não são nomes de algoritmo reconhecidos.

A versão 4 tornou obrigatória a lista de algoritmos: jwt.ParseSigned(token, nil) falha com no signature algorithms specified, de modo que um token só pode ser analisado com algoritmos que você escolheu. Tokens sem assinatura podem ser analisados se none estiver nessa lista, mas Claims(key) nunca os verifica — a única forma de ler um deles é a função de nome explícito UnsafeClaimsWithoutVerification.

A verificação é dividida em duas etapas, e é aí que está a armadilha. token.Claims(key, &claims) verifica a assinatura e preenche a struct — e retornou, sem nenhum erro, as claims de um token que tinha expirado uma hora antes. Expiração, início da validade (not-before), emissor e público-alvo só são verificados por claims.Validate() com um valor jwt.Expected ou por ValidateWithLeeway, e um token sem exp passa até por essa verificação; por isso, confira se Expiry está definido, como faz o exemplo. Validate admite jwt.DefaultLeeway, um minuto, o que o teste confirmou.

As chaves são tipadas. Um token HS256 que usava o texto PEM como chave falhou contra a *ecdsa.PublicKey com error in cryptographic primitive, mas foi verificado quando os mesmos bytes PEM foram passados como chave — mantenha as chaves como chaves Go já analisadas, e não como bytes. O ES256 recusa uma chave P-384 com expected 256 bit key, got 384 bits instead. Uma assinatura ES256 codificada em DER falha com o mesmo erro genérico error in cryptographic primitive de uma assinatura forjada; o depurador de JWT mostra o tamanho da assinatura.

Para tokens criptografados, o inspetor de JWE mostra o cabeçalho e os algoritmos; para conjuntos de chaves, o verificador de JWKS mostra o que um provedor publica, e o conversor de JWK converte chaves entre JWK e PEM.

Os fatos e as verificações por trás deles

Nada abaixo foi copiado da documentação. A verificação content/libraries/verify/go-jose/run.sh executou go-jose 4.1.6 na imagem de contêiner golang:1.27.3-trixie em 20 de outubro de 2026; a página precisa corresponder ao que a verificação imprimiu, e cada fato lista essas linhas.

  • Algoritmos JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

    Assinatura e verificação funcionaram com HS, RS, PS e ES em 256/384/512 bits e com EdDSA; ES256K, Ed25519 e Ed448 falham com unknown/unsupported algorithm.

    Registrado pela verificação
    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
  • Codificação da assinatura ES256

    raw

    As assinaturas ES256 são 64 bytes de r‖s brutos.

    Registrado pela verificação
    fact jws-ecdsa-encoding = raw
  • Assinatura DER dentro de um JWS

    rejected

    Uma assinatura ES256 codificada em DER falha com go-jose/go-jose: error in cryptographic primitive.

    Registrado pela verificação
    fact jws-der-signature = rejected
    evidence jws-der-signature: go-jose/go-jose: error in cryptographic primitive
  • Lista de algoritmos permitidos

    required

    jwt.ParseSigned exige os algoritmos permitidos; uma lista vazia falha com no signature algorithms specified.

    Registrado pela verificação
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: ParseSigned with an empty algorithm list: go-jose/go-jose: no signature algorithms specified
  • Tokens sem assinatura (alg none)

    rejected

    Com none na lista, um token sem assinatura é analisado, mas Claims() falha tanto com uma chave EC quanto com um segredo HMAC; só UnsafeClaimsWithoutVerification consegue lê-lo.

    Registrado pela verificação
    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
  • Chave pública usada como segredo HMAC

    rejected

    Um token HS256 que usava o texto PEM como chave falhou contra a *ecdsa.PublicKey; ele só foi verificado quando os próprios bytes PEM foram passados como chave.

    Registrado pela verificação
    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>
  • Curva conferida com o alg

    enforced

    Assinar ES256 com uma chave P-384 falha com expected 256 bit key, got 384 bits instead.

    Registrado pela verificação
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: go-jose/go-jose: expected 256 bit key, got 384 bits instead
  • Claims validadas na decodificação

    no

    Claims() retornou sem erro as claims de um token que tinha expirado uma hora antes; as verificações de tempo e de emissor só acontecem em Validate.

    Registrado pela verificação
    fact jwt-claims-validated-on-decode = no
    evidence jwt-claims-validated-on-decode: Claims() on a token expired an hour ago: <nil>
  • Tolerância padrão de diferença de relógio

    60s

    Validate admite jwt.DefaultLeeway, um minuto: um token expirado 57 segundos antes passou, e um expirado 63 segundos antes falhou.

    Registrado pela verificação
    fact jwt-clock-skew = 60s
    evidence jwt-clock-skew: jwt.DefaultLeeway = 1m0s
  • Claim de expiração obrigatória

    no

    Um token sem exp passou por Validate; confira você mesmo se Expiry não é nil.

    Registrado pela verificação
    fact jwt-exp-required = no

Uso

Assine claims registradas com ES256, analise o token com a lista de algoritmos, verifique a assinatura e, depois, valide as claims separadamente.
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)
}

O executor de trechos de código roda cada exemplo literalmente na imagem de contêiner golang:1.27.3-trixie, e a execução falha se a saída não corresponder.

Comando
GOFLAGS=-mod=mod go run .
A saída deve corresponder a (expressão regular)
valid: true sub: user-42

Bibliotecas