Kerberos、LDAP、SAML、WebAuthn、RADIUS は、順次置き換わる5世代のログインプロトコルではありません。これらはそれぞれ、チケット、ディレクトリ操作、システム間アサーション、公開鍵資格証明、ネットワークアクセスを整理するものであり、同じシステム内で協調して動作することができます。比較する際には、まず「誰が資格証明を検証し、誰が認可の決定を行い、どのコンポーネントが最終的にその決定を実行するのか?」という問いを立ててください。
メカニズム 主要な対象 信頼の根拠 一般的な使用箇所
Kerberos プリンシパル、チケット、セッションキー KDC とサービスの長期鍵 ドメイン内サービス認証と SSO
LDAP ディレクトリエントリと接続認証状態 ディレクトリの認証方式および ACL アカウント、グループ、属性の照会
SAML 2.0 身份アサーションおよびブラウザ SSO SP が信頼する IdP のメタデータと署名鍵 企業連合ログイン
WebAuthn RP スコープに基づいて使用される公開鍵資格証明 登録済み公開鍵と認証トランザクション ユーザーログイン、再認証
RADIUS ネットワークアクセス認証およびポリシー属性 NAS、認証サーバー、および具体的な認証方法 Wi-Fi、VPN、スイッチのアクセス制御
パスワードが存在するかどうかは、比較の次元の一つに過ぎません。鍵の登録方法や失効方法、アカウントの復旧方法、確立されたセッションの無効化方法も、システム動作を決定する重要な要素です。
Kerberos:チケットは再送可能な完全なログイン証明ではない
KDC には、AS(Authentication Server)と TGS(Ticket Granting Server)という2つの論理的な役割が含まれます。クライアントはまず AS との交換で TGT(Ticket Granting Ticket)を取得し、次に TGT を使用して TGS にターゲットサービスのチケットを要求し、最後にサービスに AP(Application Server)リクエストを送信します。チケットは有効期間中はキャッシュされ、ファイルにアクセスするたびに TGS に再アクセスする必要はありません。
Kerberos:チケットは受信側で復号され、セッションキーはクライアントが保有していることを証明するために使用されます
AS 交換:TGT とクライアント—TGS セッションキーの取得
TGT は TGS 鍵で保護されます。クライアントは保護されたセッションキーも取得します。
TGS 交換:ターゲットサービスチケットと新しいセッションキーの取得
TGT + authenticator を提出。サービスチケットはキャッシュして再利用可能。
AP 交換:ターゲットサービスに対して現在セッションキーを保有していることを証明
サービスチケット + 新しい authenticator。必要に応じて AP-REP で双方向認証。
チケットの復号が可能でも、プリンシパル、時刻、ターゲット、authenticator、リプレイのチェックが必要です。認証後に認可を実行します。
対象 保護または使用主体 なぜクライアントは別のセッションキーを必要とするのか
TGT 暗号化部分は TGS の長期鍵で保護 クライアントは TGT の読み取りだけでは秘密情報を取得できないため、AS 応答内の保護されたセッションキーを必要とする
サービスチケット 暗号化部分はターゲットサービスの長期鍵で保護 クライアントは対応するセッションキーを使用して authenticator を構築する
authenticator 対応するセッションキーで保護 現在の要求者がそのキーを保有していることを証明し、新鮮性の情報を提供する
パスワードベースの初期交換では、生パスワードは送信されませんが、パスワードから派生した長期鍵が使用され、通常は事前認証が含まれます。Kerberos は PKINIT などの公開鍵初期認証もサポートしており、「すべてのログインでパスワード鍵が1回だけ派生される」というのはプロトコルの法則ではありません。RFC 4120 、PKINIT
サービスはチケットを復号した後、ターゲットプリンシパル、有効期限、authenticator 内の身元と時刻などを照合し、同一認証器の再利用を防ぐ必要があります。タイムスタンプは許容可能なウィンドウを制限するだけであり、ウィンドウ内ではリプレイが可能 であるため、「タイムスタンプが期限切れていない」という理由だけでリプレイキャッシュやプロトコルが許可する他のリプレイ防止策に代えることはできません。許容される時計のずれは、実装とポリシーによって制御され、世界中で一律に5分固定というわけではありません。
AP-REP は双方向認証を提供し、サービスもセッションキーを掌握していることを証明します。基本的な検証では KDC へのオンラインアクセスは不要ですが、具体的なデプロイメントでは認可データの検証やその他の依存関係がある場合があります。対称鍵はサービス自体が使用できるため、「復号できれば暗号学的に KDC のみが発行可能と証明される」という主張も正確ではありません。
キャッシュは可用性を向上させますが、失効の遅延ももたらします。KDC が一時的に到達不能な場合、有効なサービスチケットは引き続き使用される可能性がありますが、新しいチケットの申請や更新は影響を受けます。サービスの長期鍵が漏洩すると、そのサービスのチケットの信頼性が損なわれます。TGS 鍵の漏洩の影響範囲はさらに大きくなります。チケット認証が完了した後、サービスは依然としてプリンシパルと認可データに基づいて、特定のディレクトリを読み取れるか、特定のアクションを実行できるかを判断する必要があります。
許容時間内なのに、なぜ2回目は拒否されるのか?
同一の時刻に2回送信した場合、2回目は時刻が正しくてもリプレイとなります。新しい認証器を生成しても、プリンシパルは依然として alice であり、再度検証することができます。受信時刻をずらすと、別の時刻チェックがトリガーされ、2つの拒否理由は別々に記録されるべきです。
図を準備しています 再読み込み
許容時間内なのに、なぜ2回目は拒否されるのか? 時間ウィンドウは古さを制限し、リプレイメモリは再利用を制限します。認証器IDの一回性要件をユーザープリンシパルに適用して、同一ユーザーの再ログインを防ぐことはできません。
{"id":"kerberos-replay-time","title":"許容時間内なのに、なぜ2回目は拒否されるのか?","summary":"時間ウィンドウは古さを制限し、リプレイメモリは再利用を制限します。認証器IDの一回性要件をユーザープリンシパルに適用して、同一ユーザーの再ログインを防ぐことはできません。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"time\" data-i18n=\"time\"></label><output for=\"time\" id=\"time-value\"></output><input id=\"time\" type=\"range\" min=\"0\" max=\"100\" value=\"20\" step=\"1\"></div><div class=\"actions\"><button id=\"send\" data-i18n=\"send\"></button><button id=\"fresh\" data-i18n=\"fresh\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><section class=\"panel\"><h3 data-i18n=\"state\"></h3><div id=\"state\"></div></section><div class=\"metrics\"><div><span data-i18n=\"result\"></span><strong id=\"result\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n#state .tr{grid-template-columns:1fr 1fr}.tr>*{min-height:3.6em}.metrics strong{font-size:17px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let id=1,stamp=20,seen=new Set,last='∅';function draw(){set('time-value',$('#time').value);table('state',[['principal','alice'],['authenticator','A'+id+' @'+stamp],['already seen',seen.has(id)?'yes':'no']]);set('result',last);$('#state').dataset.snapshot=JSON.stringify({id,stamp,seen:[...seen],last});}$('#send').onclick=()=>{const valid=Math.abs(+$('input').value-stamp)<=30;if(!valid)last='REJECT: time';else if(seen.has(id))last='REJECT: replay';else{seen.add(id);last='ACCEPT'}draw()};$('#fresh').onclick=()=>{id++;stamp=+$('input').value;draw()};$('#reset').onclick=()=>{id=1;stamp=20;seen=new Set;last='∅';draw()};$('#time').oninput=draw;draw();","audio":false,"strings":{"fresh":"同一プリンシパルで新しい認証器を生成","heading":"許容時間内なのに、なぜ2回目は拒否されるのか?","intro":"受信時刻を変更し、同一認証器の再送または同一プリンシパルの新しい認証器の送信を試みます。","reset":"リセット","result":"前回の検証結果","scope":"一般的な Kerberos 認証器リプレイの教育モデル。許容±30s。チケット/認証器の暗号化と身元チェックは完了済み。ID は一意に識別可能な認証器タプルを表し、実際のキャッシュキーは単にオンラインIDフィールドを追加したものではない。","scopeLabel":"モデルの範囲と規約","send":"現在の認証器を送信","state":"認証器とリプレイ状態","time":"受信時刻(秒)"}}
LDAP:ディレクトリ照会と接続アイデンティティは2層の状態
LDAP エントリには DN(Distinguished Name)があります。例:uid=alice,ou=people,dc=example,dc=com。search はベース DN、スコープ、フィルター、属性セットを指定します。DN はディレクトリの位置識別子であり、リネームや移動に伴って変更される可能性があります。ディレクトリアカウントの長期関連付けは、デプロイメントが提供する安定した識別子に基づいて設計する必要があります。RFC 4511
一般的な「search-then-bind」フローは、アプリケーションが制限付き権限を持つサービスIDでユーザーを検索し、正確に1件のレコードに一致することを確認し、その後、返された DN とユーザーパスワードを使用して認証を行うものです。ユーザー入力を直接 DN に連結すると、ディレクトリ構造に適合しない可能性があります。検索結果が複数件返された場合、任意に最初の結果を選択することはできません。
Bind 方式 状態の意味 注意すべき境界
空 DN、空パスワード simple bind 匿名状態 特定のユーザーの認証成功を意味しない
非空 DN、空パスワード simple bind サーバーが許可する場合、未認証の匿名状態を確立可能 成功結果をその DN のパスワード検証と見なしてはならない
非空 DN、非空パスワード simple bind ユーザー名/パスワード認証 正しい検証を前提とした TLS などの通信の保護に依存
SASL bind 選択された SASL メカニズムによって決定 SASL はフレームワークであり、すべてのメカニズムがパスワードを伝送しない、または自動的に強力であるわけではない
ログイン画面では、空パスワードによるユーザー名/パスワード認証パスへの進入を拒否する必要があります。StartTLS を使用する際は、アップグレードとサーバー証明書検証が完了してから資格証明を送信することを確認してください。アップグレード失敗後に引き続き bind を行うことを同等のパスとして扱ってはなりません。EXTERNAL は外部で既に確立された安全なアイデンティティを利用できますが、証明書アイデンティティからディレクトリ認可アイデンティティへのマッピングは依然として必要です。RFC 4513
bind は接続の認証状態を変更します。共有検索接続で一時的に特定のユーザーに bind すると、後続のリクエストに影響を与えます。認証接続とサービス照会接続には明確なライフサイクルが必要です。再 bind に失敗しても、元のサービスアカウントの権限が保持されていると仮定してはなりません。
フィルターエスケープと DN エスケープは同一のルールではない
(uid=alice*) におけるアスタリスクにはパターン意味があります。ユーザー名に実際にアスタリスクが含まれている場合、フィルター構文に従ってエンコードする必要があります。以下では、UTF-8 値の各バイトをエスケープし、形式は冗長ですが、フィルター構造への注入がないことを直接確認しやすくなります。実際のアプリケーションでは、LDAP ライブラリのフィルター値エンコード関数を使用します。
def filter_value ( text ) :
return ' ' . join ( ' \\ ' + format ( b , ' 02x' ) for b in text . encode ( ' utf-8' ) )
assert filter_value ( ' a*(b)\\ ' ) == r ' \61 \2 a\28 \62 \29 \5 c'
assert filter_value ( ' 中' ) == r ' \e 4\b 8\a d'
assert filter_value ( ' \0 ' ) == r ' \0 0'
query = ' (uid=' + filter_value ( ' alice*' ) + ' )'
assert query == r ' ( uid=\61 \6 c\69 \63 \65 \2 a) '
print ( ' フィルター値はバイトエンコーディングで通過;DN や属性名には使用しない' )
この関数は属性値のみを処理し、ユーザーが検索ベース、属性名、またはフィルター演算子を任意に選択できるようにするものではありません。RFC 4515
SAML:どの署名要素、どのリクエストを信頼するか
一般的な SP 開始型 Web SSO では、SP はリクエスト ID を保存し、ブラウザ経由で AuthnRequest を IdP に送信します。IdP は適切なユーザー認証を完了した後、通常、ブラウザ POST 経由で Response を SP の ACS(Assertion Consumer Service)に送信します。SP は応答とアサーションを検証し、独自のセッションを作成します。ブラウザが運ぶ Base64 XML は、本質的に信頼できるコンテンツではなく、暗号化されている意味でもありません。SAML Profiles
確認項目 回答される質問
信頼される IdP と署名鍵 事前に信頼が確立された発行者からのものか
署名が参照する要素 プログラムが読み取る身份属性が、実際に検証された要素内のデータと一致するか
Audience アサーションが本 SP に適用されるか
Recipient / Destination 応答または主体確認が、期待される受信先を指しているか
InResponseTo 未完了の SP リクエストに関連付けられているか(要求応答モードに適応)
NotBefore / NotOnOrAfter 許容される使用時間範囲内にあるか
Assertion / Response ID とキャッシュ 今回受け入れ可能なメッセージが既に使用済みかどうか
XML Signature Wrapping の核心的な問題は、「検証チェック A を行い、ビジネスロジックでは B を読み取る」ことです。ドキュメント内に有効な署名が1つ存在するだけでは不十分です。成熟した SAML ライブラリを使用し、消費されるアサーションと署名検証結果が同じ構造オブジェクトであることを確保する必要があります。要素 ID、解析、および許可される署名構造についても、ライブラリとプロファイルの要件に従って処理する必要があります。
NameID は主体の識別子であり、一回性消費されるログイン乱数ではありません。 その形式や issuer などの制限は、アイデンティティマッピングに影響を与えます。永続的な NameID は複数のログインに使用できます。リプレイ防止は、リクエスト ID、アサーション/レスポンス ID、有効期限に関連付ける必要があり、あるユーザーが最初にログインした後に「NameID が使用済み」と永久にマークすることはできません。
IdP 開始型の非要求応答ログインでは、一致する元の SP リクエストが存在しないため、そのプロファイルの処理ポリシーを明示的に採用する必要があります。互換性のためにすべてのコールバックの InResponseTo チェックをオフにするのは不適切です。RelayState はアプリケーション状態の回復に一般的に使用されますが、任意の外部サイトへのジャンプアドレスとして直接使用することはできません。SAML Core
以下では、署名検証およびその他の条件検証が完了した後の一回性消費のみを示します。実際のサービスでは、共有ストレージ内でアトミックにチェックして登録し、対応する受け入れウィンドウの終了まで保持する必要があります。
seen = set ( )
def consume ( issuer , assertion_id , expires , now ) :
key = ( issuer , assertion_id )
if now >= expires or key in seen :
return False
seen . add ( key )
return True
assert consume ( ' idp-a' , ' assertion-1' , 200 , 100 )
assert not consume ( ' idp-a' , ' assertion-1' , 200 , 101 )
assert consume ( ' idp-a' , ' assertion-2' , 200 , 101 ) assert not consume ( ' idp-a' , ' assertion-3' , 200 , 200 )
print ( ' アサーションの重複と期限切れの境界を通過;NameID は一回性消費に関与しない' )
WebAuthn:資格証明のスコープ、バイト署名、ユーザー検証
登録時、RP は challenge を発行し、ブラウザと認証器が navigator.credentials.create() を完了します。RP は登録結果を検証し、credential ID、公開鍵、ユーザーハンドル、および必要な状態を関連付けて保存します。認証時には navigator.credentials.get() を使用し、認証器は当該資格証明がどのローカルアカウントに属するかを知る必要があります。新しい資格証明の追加にも、信頼性の高い登録認可が必要です。
WebAuthn 認証:サーバーが検証するのは生バイトと期待されるコンテキスト
clientDataJSON
type、challenge、origin
authenticatorData
rpIdHash、flags、signCount …
SHA-256(clientDataJSON)
署名検証入力 = authenticatorData || clientDataHash
origin、challenge、RP ID は個別に照合する必要があります。署名検証の成功はトランザクション関連付けに代わるものではありません。
認証署名の入力は、authenticatorData || SHA256(clientDataJSON) の生バイトです。clientDataJSON には、ceremony 種別、challenge、origin などを含みます。authenticatorData には、RP ID ハッシュ、flags、カウンターなどが含まれます。生 JSON を解析して再シリアライズしてからダイジェストを計算してはなりません。空白やフィールド順序の変化はバイト列を変更するためです。
RP は、期待される challenge、許可された origin、RP ID ハッシュ、資格証明とアカウントの関連付け、およびポリシーで要求される UP/UV フラグを照合し、その後、資格証明の公開鍵とアルゴリズムに基づいて署名を検証する必要があります。UP はユーザーの対話的存在を示し、UV はローカルユーザー検証の完了を示します。デバイスをタッチすることが必ずしも指紋や PIN 検証に相当するわけではありません。WebAuthn 検証プロセス
import hashlib
import json
import struct
rp_id = ' example.com'
raw_client_data = json . dumps ( {
' type' : ' webauthn.get' , ' challenge' : ' example-only' ,
' origin' : ' https://login.example.com'
} , separators = ( ' ,' , ' :' ) ) . encode ( )
auth_data = hashlib . sha256 ( rp_id . encode ( ) ) . digest ( ) + bytes ( [ 0x 05] )
auth_data += struct . pack ( ' >I' , 0 )
signed_bytes = auth_data + hashlib . sha256 ( raw_client_data ) . digest ( )
assert len ( auth_data ) == 37 and len ( signed_bytes ) == 69
assert auth_data [ 32 ] & 0x 01 and auth_data [ 32 ] & 0x 04 assert hashlib . sha256 ( raw_client_data ) . digest ( ) != hashlib . sha256 (
json . dumps ( json . loads ( raw_client_data ) , indent = 2 ) . encode ( ) ) . digest ( )
print ( ' 署名入力 69 バイト;ここではエンコーディングのみをチェックし、実際の署名検証とトランザクション検証に代わるものではない' )
RP ID は通常、有効なドメイン名です。origin にはプロトコル、ホスト、および適用可能なポートが含まれます。login.example.com は規則に適合する場合、example.com を RP ID として使用できますが、RP は依然として許可された origin を制限する必要があります。これらを単なる文字列の完全一致として唯一のチェックとすることはできません。
Passkey は「常に1台のハードウェアに閉じ込められる」わけではない
Passkey は同期資格証明也可以是、単一デバイスにバインドされます。同期プロバイダーは、その保護メカニズムの下で、複数のデバイスで資格証明を使用します。これはデバイスバインド鍵のライフサイクルとは異なります。WebAuthn は、すべての秘密鍵がエクスポート不可能なハードウェアセキュリティモジュール(HSM)に存在しなければならないことを規定していません。生体認証も必須条件ではなく、ユーザー検証はデバイスの PIN 等方式で完了でき、RP は通常、生体特徴の生データを取得しません。FIDO Passkeys
概念 役割 導出すべきでない結論
Discoverable credential 認証器がアカウント資格証明を発見可能で、ユーザー名の事前入力なしでサポート すべての資格証明がクラウド同期されなければならない
Attestation 形式と信頼チェーンに基づき、認証器に関する証拠を提供 自動的に特定の自然人の身元が証明される
Backup eligibility / state バックアップ資格と現在のバックアップ状態を示す 特定のクラウドサービスブランドに等しい
signCount 複製や順序入れ替えのシグナルを提供 0 は必ず不正であり、増加しない場合は必ず攻撃である
カウンターを実装していない認証器は、常に 0 を返す可能性があります。その他の場合、増加しないことは、複製、故障、または並列応答の順序入れ替えに関与している可能性があり、仕様とリスクポリシーに従って処理する必要があります。challenge の一回性使用は、トランザクションの新鮮性の核心の一つです。
ドメイン名スコープにより、偽装サイトは、パスワードや OTP を収集する場合のように、実際の RP 用の認証結果を取得できません。ただし、アカウント復旧、既にログイン中のセッション、および端末環境には、独自の信頼境界があります。パスワードを削除しても、脆弱な復旧フローが強力になるわけではありません。
署名は正しいのに、なぜログインできないのか?
初期の RP ID は example.com ですが、origin はそのログインサブドメインであり、両者が直接文字列一致することを要求するものではありません。UV を取消しして UP を保持することは、タッチによる存在が、このポリシーで要求されるユーザー検証の完了に等しくないことを示しています。他のフィールドが正しくても、これを補完することはできません。
図を準備しています 再読み込み
署名は正しいのに、なぜログインできないのか? 署名はこれらのバイトを保護しますが、それらが現在のトランザクションに適合するかをサーバーが決定するわけではありません。RP ID は origin ホストの完全な文字列と異なる場合がありますが、両者は個別に照合されます。
{"id":"webauthn-scope-and-transaction","title":"署名は正しいのに、なぜログインできないのか?","summary":"署名はこれらのバイトを保護しますが、それらが現在のトランザクションに適合するかをサーバーが決定するわけではありません。RP ID は origin ホストの完全な文字列と異なる場合がありますが、両者は個別に照合されます。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"form-field\"><span data-i18n=\"rp\"></span><input type=\"text\" id=\"rp\" value=\"example.com\" maxlength=\"40\" autocomplete=\"off\" spellcheck=\"false\"></label><label class=\"form-field\"><span data-i18n=\"origin\"></span><input type=\"text\" id=\"origin\" value=\"https://login.example.com\" maxlength=\"50\" autocomplete=\"off\" spellcheck=\"false\"></label><label class=\"form-field\"><span data-i18n=\"challenge\"></span><input type=\"text\" id=\"challenge\" value=\"C42\" maxlength=\"12\" autocomplete=\"off\" spellcheck=\"false\"></label><label class=\"check\"><input type=\"checkbox\" id=\"up\" checked><span data-i18n=\"up\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"uv\" checked><span data-i18n=\"uv\"></span></label><div class=\"track-row\"><span data-i18n=\"checks\"></span><div id=\"checks\" class=\"track\"></div></div><div class=\"metrics\"><div><span data-i18n=\"result\"></span><strong id=\"result\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.metrics strong{font-size:18px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function draw(){const checks={RP:$('#rp').value==='example.com',origin:$('#origin').value==='https://login.example.com',challenge:$('#challenge').value==='C42',UP:$('#up').checked,UV:$('#uv').checked};cells('checks',Object.entries(checks).map(([text,ok])=>({text,cls:ok?'on':'error'})));set('result',Object.values(checks).every(Boolean)?'ACCEPT':'REJECT');$('#result').dataset.checks=JSON.stringify(checks);}document.querySelectorAll('input').forEach(e=>e.oninput=draw);draw();","audio":false,"strings":{"challenge":"clientDataJSON.challenge","checks":"個別に検証","heading":"署名は正しいのに、なぜログインできないのか?","intro":"RP ID、origin、challenge、UP/UV を個別に変更し、認証器データとクライアントデータの2つのグループの制約を観察します。","origin":"clientDataJSON.origin","reset":"リセット","result":"認証結果","rp":"authenticatorData の RP ID","scope":"資格証明の署名とアカウントバインディングは検証済み。期待される RP ID example.com、origin https://login.example.com、challenge C42、UP と UV を要求。スイッチはサーバー側の条件のみをシミュレートし、実際の認証器はトリガーしない。","scopeLabel":"モデルの範囲と規約","up":"UP:ユーザーの対話的存在","uv":"UV:ユーザー検証"}}
RADIUS と EAP:認証サーバーが決定し、NAS が実行する
企業ネットワークアクセス:EAP メソッドと RADIUS 伝送は別々に見る
端末 supplicant
ユーザー/デバイス資格証明を保有
AP / スイッチ NAS
ネットワークアクセス制御と VLAN を実行
認証サーバー
EAP メソッドとポリシーを検証
EAPOL
RADIUS / EAP-Message
Access-Accept 後、NAS は返されたアクセスポリシーを適用する必要があります
EAP-TLS の TLS 認証と NAS から RADIUS への通信の保護は、異なるプロトコル境界にあります。
典型的な 802.1X では、端末は EAPOL を介して AP/スイッチと対話し、NAS は EAP メッセージを RADIUS 属性にラップして認証サーバーに送信します。EAP は拡張可能な認証フレームワークであり、EAP-TLS はその中で TLS を使用するメソッドです。すべての EAP を暗号化トンネルと呼ぶことはできません。RADIUS/EAP 、EAP-TLS 1.3
RADIUS Access-Request には、Access-Challenge、Access-Accept、または Access-Reject が返されます。複数ラウンドの challenge は、State などの属性を使用して認証プロセスに関連付けられます。Accept には、VLAN、フィルタリング、またはセッションタイムアウトなどの認可属性が含まれる場合があります。認証は通過したが VLAN が正しく適用されていない場合、パスワードを再入力するだけでなく、NAS の実行と属性サポートを確認する必要があります。
古典的な RADIUS/UDP では、1812 番ポートが認証、1813 番ポートが課金に一般的に使用され、基本ヘッダーは 20 バイトで、Code、Identifier、Length、および 16 バイトの Authenticator を含みます。Authenticator の具体的な構成はメッセージタイプに依存し、「乱数」や「全体暗号化」と一律に呼ぶことはできません。共有シークレットと従来の属性保護は、メッセージ全体に現代的な通信の機密性があることを意味しません。RFC 2865
RADIUS/TLS または DTLS は、NAS/プロキシとサーバー間の転送を保護しますが、これは端末から EAP サーバーへの EAP-TLS とは別の境界です。RFC 9765 で定義されている RADIUS/1.1 は実験的仕様 であり、TLS/DTLS 上の ALPN 交渉を通じて、そのモードの共有シークレット、MD5 パケット認証、属性難読化メカニズムを削除します。すべての既存の UDP RADIUS がこれによってアップグレードされたとは主張できません。RADIUS/1.1
RADIUS サーバーは、ローカル設定、証明書、ディレクトリ、またはその他のアイデンティティソースを使用でき、アカウントデータを LDAP/AD に配置する必要はありません。課金記録はセッションイベントと統計を記述しますが、厳密な1回性の台帳を自然に構成するものではなく、アプリケーションは再送、欠落、およびセッション関連付けを処理する必要があります。
TACACS+ は、デバイス管理における認証、コマンドごとの認可、および課金に一般的に使用されます。その伝統的なプロトコルヘッダーは平文で、本文は共有シークレットに基づく難読化を使用するため、「全体が現代的に暗号化されている」と単純化することはできません。特定の RADIUS デプロイメントを置き換えるかどうかは、デバイスの機能と管理ニーズによって異なります。RFC 8907
これらを組み合わせてアイデンティティシステムを構築する
ある企業は、LDAP でディレクトリを保存し、Kerberos でドメインサービスにアクセスし、IdP が WebAuthn でユーザーを認証し、Web アプリケーションに SAML または OIDC 結果を発行します。オフィスネットワークは RADIUS/EAP によってアクセス制御されます。これらのコンポーネントは人員のライフサイクルを共有しますが、同じセッション状態を自動的に共有するわけではありません。
従業員が退職した後、ディレクトリの無効化、チケットの残存有効期間、IdP セッション、アプリケーションセッション、登録済み passkey、およびネットワークアクセスセッションはそれぞれ変化します。完全な設計では、各状態の保持者、有効期間、および失効伝播方法を明確にする必要があります。プロトコルの選択後、このアイデンティティライフサイクルを依然として結びつける必要があります。