Zum Inhalt springen
ecdsa.com
Go

go-jose

go-jose, die JOSE-Implementierung für Go, die Projekte wie Kubernetes, Vault und Dex nutzen: Seit v4 sind Algorithmuslisten beim Parsen Pflicht, Signaturprüfung und Validierung der Claims sind zwei getrennte Aufrufe, und die Toleranz (Leeway) beträgt standardmäßig eine Minute.
Lizenz
Apache-2.0
Geprüfte Version
4.1.6 ·
Quellcode
github.com/go-jose/go-jose
Dokumentation
pkg.go.dev/github.com/go-jose/go-jose/v4

Kompatibilität auf einen Blick

JOSE und JWT

JWS-Algorithmen
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

github.com/go-jose/go-jose/v4 (die Fortführung von Squares go-jose) implementiert JWS, JWE, JWK und JWT und taucht deshalb überall auf, wo Go-Dienste verschlüsselte oder signierte Tokens austauschen. In 4.1.6 funktionierten Signieren und Prüfen mit den Familien HS, RS, PS und ES sowie mit EdDSA; ES256K, Ed25519 und Ed448 erkennt die Bibliothek nicht als Algorithmusnamen.

Version 4 hat die Algorithmusliste zur Pflicht gemacht: jwt.ParseSigned(token, nil) scheitert mit no signature algorithms specified, ein Token lässt sich also nur mit Algorithmen parsen, die du selbst gewählt hast. Unsignierte Tokens lassen sich parsen, wenn none in dieser Liste steht, doch mit Claims(key) bestehen sie die Prüfung nie – lesen lässt sich ein solches Token nur über das ausdrücklich so benannte UnsafeClaimsWithoutVerification.

Die Prüfung läuft in zwei getrennten Schritten, und genau das ist die Falle. token.Claims(key, &claims) prüft die Signatur und füllt das Struct – es gab die Claims eines Tokens, das eine Stunde zuvor abgelaufen war, ohne jeden Fehler zurück. Ablaufzeit, Gültigkeitsbeginn (not-before), Aussteller und Audience prüft erst claims.Validate() mit einem Wert vom Typ jwt.Expected oder ValidateWithLeeway, und ein Token ohne exp besteht sogar das – prüfe also, ob Expiry gesetzt ist, wie es das Beispiel tut. Validate erlaubt jwt.DefaultLeeway, also eine Minute, was die Prüfung bestätigte.

Schlüssel sind typisiert. Ein HS256-Token mit dem PEM-Text als Schlüssel scheiterte am *ecdsa.PublicKey mit error in cryptographic primitive, bestand die Prüfung aber, als dieselben PEM-Bytes als Schlüssel übergeben wurden – verwende Schlüssel als geparste Go-Schlüssel, nicht als Bytes. ES256 lehnt einen P-384-Schlüssel mit expected 256 bit key, got 384 bits instead ab. Eine DER-kodierte ES256-Signatur scheitert mit demselben allgemeinen error in cryptographic primitive wie eine Fälschung; der JWT-Debugger zeigt die Länge der Signatur.

Bei verschlüsselten Tokens zeigt der JWE-Inspektor den Header und die Algorithmen; bei Schlüsselsätzen zeigt der JWKS-Checker, was ein Anbieter veröffentlicht, und der JWK-Konverter wandelt Schlüssel zwischen JWK und PEM um.

Fakten und die Prüfungen dahinter

Nichts im Folgenden ist aus einer Dokumentation abgeschrieben. Das Prüfskript content/libraries/verify/go-jose/run.sh hat go-jose 4.1.6 am 20. Oktober 2026 im Container-Image golang:1.27.3-trixie ausgeführt; die Seite muss mit seiner Ausgabe übereinstimmen, und jeder Fakt führt die zugehörigen Zeilen auf.

  • JWS-Algorithmen

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

    Signieren und Prüfen funktionierten mit HS, RS, PS und ES bei 256/384/512 Bit sowie mit EdDSA; ES256K, Ed25519 und Ed448 scheitern mit unknown/unsupported algorithm.

    Von der Prüfung aufgezeichnet
    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
  • Kodierung von ES256-Signaturen

    raw

    ES256-Signaturen bestehen aus 64 Byte rohem r‖s.

    Von der Prüfung aufgezeichnet
    fact jws-ecdsa-encoding = raw
  • DER-Signatur in einer JWS

    rejected

    Eine DER-kodierte ES256-Signatur scheitert mit go-jose/go-jose: error in cryptographic primitive.

    Von der Prüfung aufgezeichnet
    fact jws-der-signature = rejected
    evidence jws-der-signature: go-jose/go-jose: error in cryptographic primitive
  • Allowlist der Algorithmen

    required

    jwt.ParseSigned verlangt die zulässigen Algorithmen; eine leere Liste scheitert mit no signature algorithms specified.

    Von der Prüfung aufgezeichnet
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: ParseSigned with an empty algorithm list: go-jose/go-jose: no signature algorithms specified
  • Unsignierte Tokens (alg none)

    rejected

    Steht none in der Liste, lässt sich ein unsigniertes Token parsen, doch Claims() scheitert sowohl mit einem EC-Schlüssel als auch mit einem HMAC-Secret; lesen lässt es sich nur mit UnsafeClaimsWithoutVerification.

    Von der Prüfung aufgezeichnet
    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
  • Öffentlicher Schlüssel als HMAC-Secret

    rejected

    Ein HS256-Token mit dem PEM-Text als Schlüssel scheiterte am *ecdsa.PublicKey; die Prüfung bestand es nur, als die PEM-Bytes selbst als Schlüssel übergeben wurden.

    Von der Prüfung aufgezeichnet
    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>
  • Abgleich der Kurve mit alg

    enforced

    Das Signieren mit ES256 und einem P-384-Schlüssel scheitert mit expected 256 bit key, got 384 bits instead.

    Von der Prüfung aufgezeichnet
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: go-jose/go-jose: expected 256 bit key, got 384 bits instead
  • Validierung der Claims beim Dekodieren

    no

    Claims() gab die Claims eines Tokens, das eine Stunde zuvor abgelaufen war, ohne Fehler zurück; Zeitangaben und Aussteller werden erst in Validate geprüft.

    Von der Prüfung aufgezeichnet
    fact jwt-claims-validated-on-decode = no
    evidence jwt-claims-validated-on-decode: Claims() on a token expired an hour ago: <nil>
  • Standardtoleranz für Uhrabweichungen

    60s

    Validate erlaubt jwt.DefaultLeeway, also eine Minute: Ein 57 Sekunden zuvor abgelaufenes Token bestand die Prüfung, ein 63 Sekunden zuvor abgelaufenes scheiterte.

    Von der Prüfung aufgezeichnet
    fact jwt-clock-skew = 60s
    evidence jwt-clock-skew: jwt.DefaultLeeway = 1m0s
  • Ablauf-Claim erforderlich

    no

    Ein Token ohne exp bestand Validate; prüfe selbst, dass Expiry nicht nil ist.

    Von der Prüfung aufgezeichnet
    fact jwt-exp-required = no

Verwendung

Signiere registrierte Claims mit ES256, parse das Token mit der Algorithmusliste, prüfe die Signatur und validiere danach getrennt die Claims.
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)
}

Die Snippet-Testumgebung führt jedes Beispiel unverändert im Container-Image golang:1.27.3-trixie aus, und der Lauf schlägt fehl, wenn die Ausgabe nicht übereinstimmt.

Befehl
GOFLAGS=-mod=mod go run .
Ausgabe muss passen (regulärer Ausdruck)
valid: true sub: user-42

Bibliotheken