Saltar al contenido
ecdsa.com
Go

go-jose

go-jose, la implementación de JOSE para Go que usan proyectos como Kubernetes, Vault y Dex: desde la v4, las listas de algoritmos son obligatorias al analizar, la comprobación de la firma y la validación de los claims son dos llamadas distintas, y la tolerancia predeterminada es de un minuto.
Licencia
Apache-2.0
Versión comprobada
4.1.6 ·
Código fuente
github.com/go-jose/go-jose
Documentación
pkg.go.dev/github.com/go-jose/go-jose/v4

Compatibilidad de un vistazo

JOSE y JWT

Algoritmos JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

github.com/go-jose/go-jose/v4 (la continuación del go-jose de Square) implementa JWS, JWE, JWK y JWT, y por eso aparece allí donde los servicios Go intercambian tokens cifrados o firmados. Con la 4.1.6 se firmó y se verificó con las familias HS, RS, PS y ES y con EdDSA; ES256K, Ed25519 y Ed448 no son nombres de algoritmo reconocidos.

La versión 4 hizo obligatoria la lista de algoritmos: jwt.ParseSigned(token, nil) falla con no signature algorithms specified, de modo que un token solo se puede analizar con los algoritmos que tú elijas. Los tokens sin firmar se pueden analizar si none está en esa lista, pero Claims(key) nunca los verifica: la única forma de leer uno es UnsafeClaimsWithoutVerification, cuyo nombre lo advierte expresamente.

La verificación está dividida en dos, y ahí está la trampa. token.Claims(key, &claims) comprueba la firma y rellena la estructura: devolvió sin ningún error los claims de un token que había caducado una hora antes. La caducidad, el inicio de validez, el emisor y la audiencia solo los comprueba claims.Validate() con un valor jwt.Expected o ValidateWithLeeway, y un token sin exp supera incluso eso, así que comprueba que Expiry esté establecido, como hace el ejemplo. Validate admite jwt.DefaultLeeway, un minuto, como confirmó la comprobación.

Las claves tienen tipo. Un token HS256 que usaba como clave el texto PEM falló contra la *ecdsa.PublicKey con error in cryptographic primitive, pero se verificó cuando se pasaron como clave esos mismos bytes PEM: guarda las claves como claves Go ya analizadas, no como bytes. ES256 rechaza una clave P-384 con expected 256 bit key, got 384 bits instead. Una firma ES256 codificada en DER falla con el mismo error in cryptographic primitive genérico que una falsificación; el decodificador de JWT muestra la longitud de la firma.

Para los tokens cifrados, el inspector de JWE muestra el encabezado y los algoritmos; para los conjuntos de claves, el comprobador de JWKS muestra lo que publica un proveedor y el conversor de JWK convierte claves entre JWK y PEM.

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/go-jose/run.sh ejecutó go-jose 4.1.6 en la imagen de contenedor golang:1.27.3-trixie el 20 de octubre de 2026; la página debe coincidir con lo que imprimió, y cada dato cita esas líneas.

  • Algoritmos JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

    Con HS, RS, PS y ES de 256/384/512 bits y EdDSA se firmó y se verificó; ES256K, Ed25519 y Ed448 fallan con unknown/unsupported algorithm.

    Registrado por la comprobación
    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
  • Codificación de la firma ES256

    raw

    Las firmas ES256 son 64 bytes de r‖s en bruto.

    Registrado por la comprobación
    fact jws-ecdsa-encoding = raw
  • Firma DER dentro de un JWS

    rejected

    Una firma ES256 codificada en DER falla con go-jose/go-jose: error in cryptographic primitive.

    Registrado por la comprobación
    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 los algoritmos permitidos; una lista vacía falla con no signature algorithms specified.

    Registrado por la comprobación
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: ParseSigned with an empty algorithm list: go-jose/go-jose: no signature algorithms specified
  • Tokens sin firma (alg none)

    rejected

    Con none en la lista, un token sin firmar se analiza, pero Claims() falla tanto con una clave EC como con un secreto HMAC; solo UnsafeClaimsWithoutVerification lo lee.

    Registrado por la comprobación
    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
  • Clave pública usada como secreto HMAC

    rejected

    Un token HS256 con el texto PEM como clave falló contra la *ecdsa.PublicKey; solo se verificó cuando se pasaron como clave los propios bytes PEM.

    Registrado por la comprobación
    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 cotejada con alg

    enforced

    Firmar ES256 con una clave P-384 falla con expected 256 bit key, got 384 bits instead.

    Registrado por la comprobación
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: go-jose/go-jose: expected 256 bit key, got 384 bits instead
  • Claims validados al decodificar

    no

    Claims() devolvió sin error los claims de un token que había caducado una hora antes; las comprobaciones de tiempo y de emisor solo se hacen en Validate.

    Registrado por la comprobación
    fact jwt-claims-validated-on-decode = no
    evidence jwt-claims-validated-on-decode: Claims() on a token expired an hour ago: <nil>
  • Desfase de reloj predeterminado

    60s

    Validate admite jwt.DefaultLeeway, un minuto: un token caducado 57 segundos antes pasó y uno caducado 63 segundos antes falló.

    Registrado por la comprobación
    fact jwt-clock-skew = 60s
    evidence jwt-clock-skew: jwt.DefaultLeeway = 1m0s
  • Claim de caducidad obligatorio

    no

    Un token sin exp superó Validate; comprueba tú mismo que Expiry no sea nil.

    Registrado por la comprobación
    fact jwt-exp-required = no

Uso

Firma claims registrados con ES256, analiza con la lista de algoritmos, comprueba la firma y después valida los claims por separado.
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)
}

El arnés de pruebas de fragmentos de código ejecuta cada ejemplo tal cual en la imagen de contenedor golang:1.27.3-trixie, y la ejecución falla si la salida no coincide.

Comando
GOFLAGS=-mod=mod go run .
La salida debe coincidir (expresión regular)
valid: true sub: user-42

Bibliotecas