本文へスキップ
ecdsa.com
Go

go-jose

go-jose は、Kubernetes、Vault、Dex などのプロジェクトが使う Go の JOSE 実装です。v4 以降は解析時にアルゴリズムの一覧が必須で、署名の検証とクレームの検証は別々の呼び出しで行い、時刻の許容誤差(leeway)のデフォルトは 1 分です。
ライセンス
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(Square の go-jose の後継)は 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 を使うことです。

検証が 2 段階に分かれていることが落とし穴です。token.Claims(key, &claims) は署名を検証して構造体に値を入れます。1 時間前に期限切れになったトークンのクレームも、エラーなしで返しました。有効期限、有効開始時刻(not-before)、発行者、オーディエンスを確認するのは、jwt.Expected の値を渡した claims.Validate() か ValidateWithLeeway だけで、exp のないトークンはそれさえ通過します。サンプルのように、Expiry が設定されていることを確認してください。Validate は jwt.DefaultLeeway(1 分)の誤差を許容し、チェックでもそれを確認しました。

鍵には型があります。PEM のテキストを鍵として作成した HS256 トークンは、*ecdsa.PublicKey に対しては error in cryptographic primitive で失敗しましたが、同じ PEM のバイト列を鍵として渡すと検証を通りました。鍵はバイト列ではなく、解析済みの Go の鍵として保持してください。ES256 は P-384 の鍵を expected 256 bit key, got 384 bits instead で拒否します。DER エンコードの ES256 署名は、偽造された署名と同じ汎用的な error in cryptographic primitive で失敗します。署名の長さは JWT デバッガーで確認できます。

暗号化されたトークンについては、JWE インスペクターでヘッダーとアルゴリズムを確認できます。鍵セットについては、JWKS チェッカーでプロバイダーが公開している内容を確認でき、JWK 変換ツールで鍵を JWK と PEM の間で変換できます。

事実とその根拠となるチェック

以下の内容はドキュメントからの転記ではありません。2026年10月20日に、チェック content/libraries/verify/go-jose/run.sh がコンテナイメージ golang:1.27.3-trixie 内で go-jose 4.1.6 を実行しました。このページの内容はその出力と一致していなければならず、各事実には該当する出力行を示しています。

  • JWS アルゴリズム

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

    256/384/512 ビットの HS、RS、PS、ES と 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 バイトの生の r‖s です。

    チェックによる記録
    fact jws-ecdsa-encoding = raw
  • JWS 内の DER 署名

    rejected

    DER エンコードの ES256 署名は 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

    PEM のテキストを鍵として作成した HS256 トークンは、*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

    P-384 の鍵で ES256 の署名を行うと 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() は、1 時間前に期限切れになったトークンのクレームをエラーなしで返しました。時刻と発行者の確認は 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(1 分)の誤差を許容します。57 秒前に期限切れになったトークンは通過し、63 秒前に期限切れになったトークンは失敗しました。

    チェックによる記録
    fact jwt-clock-skew = 60s
    evidence jwt-clock-skew: jwt.DefaultLeeway = 1m0s
  • 有効期限クレームの要否

    no

    exp のないトークンは Validate を通過しました。Expiry が nil でないことは自分で確認してください。

    チェックによる記録
    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

ライブラリ