Skip to content

セキュリティ方針 ​

このページでは、本ライブラリが どうやって 安全性を保とうとしているかと、どこまでの保証を与え、どこから先は与えていないか を整理します。「OP」は OpenID Provider、つまり本ライブラリが実装する認可サーバのこと、「OFCS」は OpenID Foundation の適合性テストスイートのことです。

期待値の合わせ方

go-oidc-provider は個人開発者が空き時間で維持している OSS ライブラリです。第三者による独立監査、有償ペネトレーションテスト、OpenID Foundation の公式認証 — いずれも受けていません。下記の性質はコードベースが能動的に防いでいるものを書き出したものであって、第三者監査レポートと同等のものではありません。

5 つの独立した性質 ​

「これは安全か?」と問われたとき、誠実に答えるには次の 5 つに分けて回答する必要があります。

#性質このプロジェクトで示せる根拠
1適合性 — 仕様を実装しているOFCS 緑(適合状況: OFCS)
2正しさ — どんな入力でも壊れないtest/ 配下のユニット / シナリオ / fuzz テスト
3セキュリティ — 能動的な攻撃に耐える後述の構造的防御と設計判断
4サプライチェーン — 依存が安全であるCI で govulncheck、depguard、サードパーティ目録
5運用 — 時間が経っても安全であるSECURITY.md による報告経路、GitHub Security Advisories

適合性 ≠ セキュリティです。OFCS が緑でも、攻撃が可能なケース(alg 混同、PKCE ダウングレード、redirect_uri 部分一致、シークレット比較のタイミング攻撃など)は存在し得ます。次のセクションでは、それぞれのクラスを if 文ではなく構造的に閉じている理由を説明します。

「これは安全か」を支える 5 つの独立した性質
適合性・正しさ・セキュリティ・サプライチェーン・運用の保証は、それぞれ独立した性質です。裏付けの取り方も別々で、ひとつについての主張が他のことを保証するわけではありません。1適合性OFCS適合スイートで実行する2正しさテスト · fuzzing仕様どおりに動くか3セキュリティ構造的な防御チェック項目の羅列ではない4サプライチェーンgovulncheck · depguardCI で強制する5運用SECURITY.md · GHSA報告が来たときに何が起きるか
並び順は読む順序であって、重要度の順ではありません。適合スイートが通ったことは 4 番目について何も語りませんし、依存関係のスキャンが綺麗なことは 3 番目について何も語りません。

構造的防御(コードの形そのもので強制している箇所) ​

「実装ミスがないか毎回検査する」のではなく、不安全な経路がそもそも存在しないように型 / パッケージ境界で設計しています。

1. コンストラクタはゼロ値起動を拒否する ​

op.New(...) は error を返します — 必須オプションが欠けるとエラーで弾かれます。WithIssuer、WithStore、WithKeyset は無条件で必須、WithCookieKeys は authorization_code grant が有効な場合(既定の grant 集合を含む)に必須です。利用可能なゼロ値の Provider は存在しません。

そのほか、設定が無視されたり初回利用時に失敗したりする構成も、構築時に拒否します。

  • 束縛済みの subject を前提とする要素(PrimaryPasskey、StepTOTP、StepEmailOTP、StepRecoveryCode)を LoginFlow.Primary に置いた構成。これらは subject を束縛する primary の後ろに Rule のステップとして置きます。
  • client_secret_basic または client_secret_post を使う、静的に登録した confidential client に secret がない構成。そのままでは空の client_secret を受理してしまいます。平文の Secret が 32 byte 未満の構成も拒否します(OIDC Core §16.19 が HS256 の鍵として使う secret に求める最小長。op.NewClientSecret が条件を満たす secret を生成します)。
  • パスワードまたは第 2 factor の step がある login flow で lockout store を解決できない構成。subject 単位の総当たり対策が、気付かれずに無効のまま動くのを防ぎます。
  • ブラウザのサインイン用オプション(WithLoginFlow、WithUI、WithInteractions、WithFirstPartyClients)と、authorization_code を含まない WithGrants の組み合わせ。
  • どの login でも生成できない acr_values_supported の entry、feature.MTLS なしの WithMTLSProxy と WithMTLSRootCAs、開発用ホストでない issuer に対する WithAllowInsecureOutboundForDev。
  • typed-nil のサブストア、2 回目の WithGrantManagement、組み込みのために予約された consent / chooser という名前の WithInteractions エントリ。
  • 負の CIBA 有効期限や poll 間隔、および FAPI-CIBA での 10 分を超える有効期限。
なぜ重要か

多くの「既定 ON」型のフレームワークは設定不足でも気付かれずに起動します。本ライブラリの設計では「OP が立ち上がったが署名鍵がない / 推測可能な cookie 鍵 / 誤った issuer」といった種類のバグを、最初の 1 リクエストが届く前に閉じます。詳細は op.WithIssuer / op.WithKeyset / op.WithCookieKeys / op.WithStore の不在時に出るビルド時エラーを参照してください。

2. JOSE の alg リストは閉じた型 ​

internal/jose.Algorithm は RS256、PS256、ES256、EdDSA の 4 値のみを列挙しています。none、HS256/384/512、その他の文字列は IsAllowed() で false を返します。ParseAlgorithm は不明値に対してフォールバックせず、ok=false を返します。生 go-jose ハンドルを保持する internal/jar、internal/dpop、internal/mtls、internal/backchannel などの経路も含めて、すべての入力 JWS 検証パスは入力された alg をこの閉じた型でゲートします。OP 発行 JWT の署名はさらに狭く、OP ごとにアルゴリズムは 1 つで、既定は ES256、WithSigningAlgorithm で RS256 にでき、discovery もその 1 つだけを広告します。alg 混同攻撃(RFC 7519 §6 / RFC 8725 §2.1)は構造的に到達できません。

想定リスク緩和策
JWT ライブラリが alg=none を受理Algorithm(s) は不明値・空文字を拒否
公開鍵経路で HS256 を受理HS* は型に存在しない
配置ごとの alg「フィーチャーフラグ」depguard の jose-isolation ルールで go-jose/v4 を直接 import できるパッケージを許可リストで固定。新規呼び出し元は明示的に追加する必要がある

3. crypto/rand のみ — math/rand 禁止 ​

リポジトリ全体ルールで math/rand を禁止しています。lint も同様に強制。すべての nonce、コード、リフレッシュトークン、認可コード、DPoP server-nonce は crypto/rand を使います。

4. time.Now() は internal/timex/ 経由のみ ​

time.Now() の直接呼び出しは forbidigo lint でブロックされ、すべての wall-clock 読みは internal/timex.Clock を経由します。唯一の正規呼び出し点は internal/timex.systemClock.Now() で、別モジュールとして版管理される op/storeadapter/redis、sql、dynamodb は internal/timex を import できないため、各モジュールに明示的な例外が 1 箇所ずつあります(組み込み側は各モジュールの WithClock オプションで fallback を上書きできます)。Clock 抽象により、時刻注入を境界で明示し、テスト時に時刻がずれて replay 検証窓のテストが偶然通ってしまう事故を防いでいます。

5. エラーは型付きカタログ経由のみ ​

fmt.Errorf で API 可視のエラーを作ることはできません。通信路に出るエラーはすべて op.Error / カタログ値から組み立てます。これにより error code、status code、WWW-Authenticate の形が一貫し、情報漏えいもカタログ範囲に収まります。

6. internal/ 境界は不可侵 ​

外部から internal/ を import することはできません。公開すべきものは op/ とそのサブパッケージ(op/profile/、op/feature/、op/grant/、op/audit/、op/authn/、op/store/、op/storeadapter/ など)の安定した公開面に出ています。「JAR verifier だけ差し替えたい」といった抜け道は Go のパッケージ可視性ルールで閉じられています。

7. ORM を持ち込まない設計 ​

ライブラリは ORM(GORM、ent、xo など)を埋め込みません。ストレージは小さな store.*Store interface を介します。呼び出し側は使い慣れたものを実装するだけです。副作用として「気付かぬうちに DB がマイグレーションされる」事態が起きません。

既定で有効な防御機能 ​

防御ソース仕様
__Host- cookie、AES-256-GCM、double-submit CSRF、Origin/Referer チェックinternal/cookie、internal/csrfOWASP ASVS L1 / RFC 6265bis
public / native の authorization-code client、および OAuth21 と FAPI 2.0 プロファイル下の全 authorization-code client に PKCE 必須internal/authorize、internal/pkceRFC 7636、RFC 9700
リフレッシュトークンのローテーション + 再利用検知internal/grants/refresh、internal/grants/teardownRFC 9700 §4.14.2
送信者制約付きトークン(DPoP cnf.jkt、mTLS cnf.x5t#S256)internal/dpop、internal/mtls、internal/tokensRFC 9449、RFC 8705
認可応答に iss を付与internal/authorizeRFC 9207
redirect_uri 完全一致(既定で完全一致)internal/authorizeOAuth 2.1、RFC 8252
ループバック redirect の制約強化internal/registrationendpoint、internal/authorizeRFC 8252
外向き HTTP fetch の制約(許可 scheme、body 上限、Accept、timeout、redirect、cache)を JAR JWKS / client-encryption JWKS / sector_identifier_uri / back-channel logout 配送に適用internal/securefetch、internal/netsecOWASP SSRF、OIDC Back-Channel Logout 1.0
OP 側の request_object replay 対策(jti)internal/jarRFC 9101 §10.8
各検証経路でのアルゴリズム許可リストinternal/joseRFC 8725
Issuer 正規化(末尾スラッシュなどの揺れを防ぐ)op/options_validate.goRFC 9207
Resource indicator の正規化(scheme と host を小文字化、default port 除去、末尾スラッシュ揃え、fragment / userinfo 拒否)を /auth、/token、/device_authorization、/bc-authorize、WithAccessTokenFormatPerAudience の許可リストに適用internal/resourceindicatorRFC 8707 §2
/token、/bc-authorize、/end_session で単一値パラメータの重複を拒否(RFC 8707 の resource= のみ例外)internal/httpx、internal/clientauth、internal/tokenendpoint、internal/cibaendpoint、internal/endsessionRFC 6749 §3.2.1、OIDC RP-Initiated Logout 1.0 §3
Argon2id パラメータを OWASP Password Storage Cheat Sheet のベースライン(memory ≥ 19 MiB、time ≥ 2)で強制(client_secret、エンドユーザパスワード、リカバリコード)internal/argon2id、internal/authn/password、internal/authn/recovery、internal/clientauth/secretOWASP Password Storage Cheat Sheet
最初の JSON 値の後ろに残ったデータ(余分な } など)を、JSON を読むすべての endpoint とドライバで拒否internal/httpx、internal/registrationendpoint、op/interactionRFC 7591 §2
有効化した grant / feature が要求するサブストアを、設定済み store が公開しない構成を op.New が拒否op/options_validate.go、op/storeadapter/redis— (defence in depth)
クライアント検証鍵(client_assertion、JAR request object)を OP の鍵 shape 最低水準に保持: RSA は 2048 bit 以上、EC 曲線は宣言された alg と一致internal/clientauth、internal/jar、internal/jose(AssertAlgKeyShape)RFC 7518 §3.3、RFC 8725 §3.2
jwks_uri を使う private_key_jwt クライアントは、未知の kid を見たときに 1 回だけ throttle 付きでキャッシュを迂回して再取得し、正当な RP 鍵ローテーションから復帰するinternal/clientauth、internal/jarRFC 7523、OIDC Dynamic Registration
private_key_jwt assertion 検証は JWS kid で鍵を選択し、kid 無しでも試行検証数を上限付きにし、鍵無し拒否のタイミングを均し、リクエスト内の client lookup を 1 回にまとめるinternal/clientauthRFC 7515 §4.1.4、RFC 7523
JWKS parse 時に秘密鍵と対称鍵を拒否し、検証経路へ渡さないinternal/clientauth、internal/jar、internal/joseRFC 8725
jwks と jwks_uri の両方を持つクライアントレコードを、鍵を読むすべての経路(request object、private_key_jwt、response 暗号化)で拒否internal/clientauth、internal/jar、internal/clientencjwksRFC 7591 §2
DPoP-bound access token を Bearer scheme で /userinfo に提示した場合は拒否。送信者制約付き token は DPoP scheme が必須internal/endpointsupport、internal/userinfoRFC 9449 §7.1
改ざん・decode 不能な authorization endpoint の session cookie は持ち越さず、clear して「session なし」と扱うinternal/authorizeendpoint、internal/cookieRFC 6265bis
ワンタイム認証 factor(email-OTP、TOTP、recovery code)を atomic な compare-and-set で単回使用化(再提示時は ErrAlreadyConsumed)internal/authn、op/store— (defence in depth)
TOTP / email-OTP / recovery code の誤入力分岐はすべて共有 retry path と observer を通るため、captcha / lockout gate が second-factor brute-force を観測できるinternal/authn— (defence in depth)
リフレッシュトークンの id / parent_id を保存時にハッシュ化し、定数時間で参照internal/tokenendpoint、op/storeRFC 9700 §4.14.2
Refresh-token rotation は child insert と同じ critical section / transaction 内で parent を再確認し、replay revocation が成功済み descendant save と競合して取り逃がすことを防ぐop/storeadapter/sql、op/storeadapter/inmemRFC 9700 §4.14.2
Token exchange は policy 適用後の決定を再検証し、付与 scope が要求済み subject-token-bounded scope 内、付与 audience が要求済み audience 内に残ることを強制するinternal/customgrant/tokenexchangeRFC 8693
token response の途中で server_error になった場合、mint 済みの永続 opaque access token row を失効させ、有効な孤児 token を残さないinternal/tokenendpoint、op/store— (defence in depth)
別の client が提示した認可コード、または redirect_uri / PKCE の検証に失敗した認可コードは、コードを消費せず、正当な保持者の grant も失効させずに拒否internal/tokenendpointRFC 6749 §4.1.2、RFC 9700
認可コード、リフレッシュ chain、アクセストークンが、共有 grant ではなく自分を発行した認可の auth_time、acr、amr、claims リクエスト、authorization_details を持つop/store、internal/tokenendpointOIDC Core 1.0 §2、RFC 9396(#32 参照)
LoginFlow のルールと Decider を、既存セッションから返す認可にも適用し、panic や不正な判定結果では拒否側に倒すinternal/authorizeendpoint、opRFC 9470(#33 参照)
id_token_hint は typ が未設定または JWT のものだけ受理し、logout token やアクセストークンが /end_session や CIBA で ID token の代わりになることを防ぐinternal/idtokenhintRFC 8725 §3.11
ストアされた hash が Argon2id ポリシーを下回るアカウントや別エンコードのアカウントでも、パスワードログインの所要時間を他のログイン失敗と揃えるinternal/authn/passwordCWE-208
WithFirstPartyClients のクライアントが同意画面を飛ばすのは、リクエストで解決したクライアントレコードが static、admin 管理、または legacy のときだけ(動的登録や metadata document のレコードは対象外)internal/authorizeendpointOIDC Core 1.0 §3.1.2.4
prompt=select_account で選ばれたアカウントも、戻ってきたセッションと同じ LoginFlow の rule、Decider、risk step-up を通るinternal/authorizeendpoint, internal/authnRFC 9470
ID Token に載るのは、認証が満たした acr だけop/acr.goOIDC Core 1.0 §2, RFC 9470
passkey の user verification は保存済み credential からの転記ではなく、assertion ごとの flag から読むinternal/authnWebAuthn Level 2 §6.1
code 交換、refresh、token exchange、device code や CIBA の poll の最中に grant が失効した場合、token の保存後に再確認し、直前に発行した credential を失効させるinternal/tokenendpointRFC 9700
署名付き /userinfo 応答には常に OP の iss とクライアントの aud が入り、claim の供給元は iss、aud などの予約済み JWT claim を差し込めないinternal/userinfo, internal/userclaimsOIDC Core 1.0 §5.1, §5.3.2
8 KiB を超える DPoP proof は、埋め込まれた jwk を解析する前に不正な proof として拒否するinternal/dpop, internal/joseRFC 9449 §4.3, RFC 7517
id_token_hint が一致する /end_session は、同じブラウザでサインインしている他のアカウントまで終了させるログアウトのとき、確認を求めるinternal/endsessionOIDC RP-Initiated Logout 1.0 §2
  • public / native の authorization-code client:plain は拒否
  • リフレッシュトークンのローテーション + 再利用検知:replay は chain と grant のアクセストークンを失効させ、ローテーションと /revoke は自分の chain に範囲を限るため、同じ client 上の他のセッションには触れない
  • 外向き HTTP fetch の制約:dial 段階で loopback / link-local / RFC 1918 / IPv6 ULA を拒否し、BaseTransport が *http.Transport 以外なら URL ゲートでフォールバック。proxy 経由の fetch は、引き渡す前に宛先ホストを URL として検査し、proxy のアドレス自体も拒否リストを通る必要があり、環境変数の proxy 設定は参照しない
  • /token、/bc-authorize、/end_session で単一値パラメータの重複を拒否:認証情報を 2 か所以上に提示したリクエストは clientauth.Parse で拒否
  • Argon2id パラメータを OWASP Password Storage Cheat Sheet のベースライン:リカバリコード一括検証は 16 件で打ち切り。エンコード済み hash の重複パラメータセグメントは拒否
  • jwks_uri を使う private_key_jwt クライアントは:ランダムな未知 kid は外向き fetch を増幅できない
  • jwks と jwks_uri の両方を持つクライアントレコードを、鍵を読むすべての経路:複数の経路が使う jwks_uri は 1 回だけ fetch してキャッシュする
  • ワンタイム認証 factor:cross-factor の lockout は compare-and-swap で更新する(AuthnLockoutStore.CompareAndSwap)
  • 別の client が提示した認可コード:失効させるのは本物の replay だけ
  • WithFirstPartyClients のクライアントが同意画面を飛ばすのは:prompt=consent、offline_access、信頼できない Sec-Fetch-Site のナビゲーション、prompt=create、アカウントチューザでは引き続き同意を求める
  • prompt=select_account で選ばれたアカウントも:既存セッションを引き継ぐ login はそのセッションの subject に固定されるため、rule の step が別のアカウントでサインインさせることはできない
  • ID Token に載るのは:op.DefaultACRPolicy は到達した水準以下の標準 AAL URI だけを満たし、満たせなかった任意(voluntary)のリクエストには実際に到達した水準を報告する
  • passkey の user verification は保存済み credential からの転記ではなく:presence のみの assertion は AAL2 に届かない
  • 署名付き /userinfo 応答には常に OP の iss とクライアントの aud が入り:JSON の型が OIDC Core §5.1 と異なる標準 claim は、claim 名ごとに警告を 1 回出して除外する
  • 8 KiB を超える DPoP proof は:デコードできても有効な鍵でないクライアントの JWK Set のメンバーは、panic せず無視する

ツールチェーン ​

階層ツール場所
Lintgolangci-lint(errcheck、govet、staticcheck、unused、gosec、errorlint、revive、depguard など).golangci.yml
脆弱性スキャンgovulncheckscripts/govulncheck.sh
FuzzGo 標準 Fuzz*(make fuzz / scripts/fuzz.sh 30s)internal/jose、internal/dpop、internal/pkce、internal/clientauth、internal/parendpoint、internal/introspectendpoint、internal/revokeendpoint、internal/jarm、internal/mtls、internal/authorizationdetails など
ライセンスgo-licensesscripts/licenses.sh
適合性make conformance-baselinetools/conformance/、conformance/
シナリオカタログに基づく Spec Scenario Suitetest/scenarios/

Go の language baseline と toolchain は core module が宣言しています。このページにリリース固有の値を写すのではなく、core の go.mod を確認してください。

ここに 無い もの ​

このプロジェクトの限界をはっきり書きます。採用前に必ず目を通してください。

採用前に必ず確認

  • ❌ 第三者によるセキュリティ監査は受けていません。
  • ❌ 有償ペネトレーションテストも未実施です。
  • ❌ OpenID Foundation の公式認証は取得していません。(OFCS 適合状況 参照)
  • ❌ パッチ適用 SLA は提供していません。 SECURITY.md には 目安 として「3 営業日以内に受領、確認済み問題は 14 日以内に緩和方針」を書いていますが、契約上の保証ではありません。
  • ❌ 24/7 のインシデント対応窓口はありません。 報告経路は GitHub Security Advisories または Maintainer プロフィールです。
  • ❌ このページは CVE の最新台帳ではありません。 Advisory の状況は変わり得るため、脆弱性報告ガイド とリポジトリの GitHub Security Advisories を現在の記録として確認してください。

これらのいずれかが採用要件に含まれているなら、本ライブラリは選ばないでください。公式監査が出るまでは控えるべきです。限界を明示しておくことは、誤用を防ぐために必要です。

採用判断のヒント ​

状況判断
自社プロダクトの内部 OP を作る適合します。go.mod でバージョンタグを固定し、自社 CI で govulncheck を回し、GHSA を購読してください
公衆向け、価値の高いフロー(銀行グレード FAPI)このライブラリは出発点として使い、第三者監査を入れて findings を還元する形を推奨
コンプライアンス上の都合で認証取得済み IdP を置換する推奨しません — 引用可能な第三者監査結果が揃うまでは控えてください
実 OP のコードベースで OIDC の仕組みを学ぶ最適。構造的防御自体が学習コンテンツになります

続きはこちら ​

  • 設計判断 — プロトコル上の選択とライブラリ独自の方針(PAR の request_uri 再利用、リフレッシュ再試行の猶予期間、OIDC Core と署名アルゴリズムの扱いなど)。
  • 脆弱性報告 — 脆弱性報告、サポート対象バージョン、CVE 取り扱い。
  • OFCS 適合状況 — 適合性が証明できること / できないこと。