一覧SC-10-1 · SSOと認証連携

SAML認証と応答の検証

正しい署名があることと、このSPが今の要求へ受け入れてよいことを分けて判断します。

説明

SAMLではIdPが認証結果を表すAssertionを発行し、サービス提供側のSPが受け取ります。SPは署名だけでなく、発行者、利用先、返送先、期限、開始した要求との対応も検証します。

署名付きXML応答を読み、正しい応答を一度受け入れます。その後、利用先や返送先を変えた署名済み応答、本文を改変した応答、同じAssertionの再提示を比較します。

正しい署名があることと、このSPが今の要求へ受け入れてよいことを分けて判断します。

1署名付きResponseを読み込む

この演習はResponse全体をRSA-SHA256で署名します。IdPの検証証明書はSPへ事前に登録した公開試料です。

from saml import * raw = issue()署名付き応答試料を作る root = E.fromstring(raw)XMLのフィールドを解析する print(root.tag,root.get("ID"),root.get("InResponseTo")) print(root.findtext("saml:Issuer",namespaces=NS))

response-7はrequest-7への応答で、発行者はidp.example.testです。受領したXMLの文字列を眺めただけでは署名検証にはなりません。

2利用先と返送先を対応付ける

AudienceはAssertionの利用先、Recipientは受領するエンドポイントです。

print(root.findtext("saml:Assertion/saml:Conditions/saml:AudienceRestriction/saml:Audience",namespaces=NS)Assertionの利用先を取得する) print(root.find("saml:Assertion/saml:Subject/saml:SubjectConfirmation/saml:SubjectConfirmationData",NS).attrib返送先・要求ID・期限を取得する) print(root.find("ds:Signature/ds:SignedInfo/ds:Reference",NS).get("URI")署名参照先を読む)

web-spとacsへの返送先、request-7、期限が含まれます。署名のReferenceは#response-7で、Response全体に結び付いています。

3署名と利用条件を検証する

SPは署名検証から返されたXMLだけを後続の判定へ使います。同名の別要素を未検証の木から選び直す構成にしません。

sp = SP() print(sp.accept(raw)検証済みXMLの利用条件を確認する)

accepted:aliceです。署名、発行者、宛先、要求ID、有効期間が、この固定のSP設定に一致しました。

4同じAssertionを再提示する

受け入れたAssertionのIDを保持してから、同じ応答を再提示します。

print(sp.accept(raw)同じ応答をもう一度提示する) print(sorted(sp.seen消費したAssertionのIDを確認する))

replayedです。署名は正しいままでも、同じ認証結果の再利用を受け入れない状態管理が必要です。

5別の利用先・返送先へ発行された応答

それぞれIdPが正しく署名した、条件だけが違う試料です。

print(SP().accept(issue(audience="other-sp"利用先だけを変える))) print(SP().accept(issue(recipient="https://other.example.test/acs"返送先を変える))) print(SP().accept(issue(request="request-8"応答先の要求を変える)))

audience、recipient、requestの不一致がそれぞれ返ります。暗号署名の成功だけで条件違いの応答を受け入れてはいけません。

6有効期限に到達した応答

NotOnOrAfterの境界は含みません。時刻をちょうど00:05:00へ進めます。

print(SP().accept(raw,now="2026-11-13T00:05:00Z"期限境界で検証する))

outside_validityです。受信側の時刻、許容する時計ずれ、Assertionを保持する期間も運用設計に必要です。

7署名後に利用者名を変更する

署名を作り直さず、NameIDのaliceをmalloryへ差し替えます。

tampered = raw.replace署名後のXML本文を変更する(b">alice<",b">mallory<") print(SP().accept(tampered)改変された応答を検証する)

signature_invalidです。署名済みResponseの内容が変わったため、属性を使う前に拒否します。

まとめ

IdPの署名とSP側の利用条件を両方検証します。

署名参照先と、利用するXML要素を一致させます。

要求ID・期限・使用済みIDで応答の流用を防ぎます。

操作履歴と解説を振り返れます。

Python 3.15 / cryptography 50.0.2 / 公開試料と固定時刻

1
from saml import * raw = issue() root = E.fromstring(raw) print(root.tag,root.get("ID"),root.get("InResponseTo")) print(root.findtext("saml:Issuer",namespaces=NS))
{urn:oasis:names:tc:SAML:2.0:protocol}Response response-7 request-7
https://idp.example.test
2
print(root.findtext("saml:Assertion/saml:Conditions/saml:AudienceRestriction/saml:Audience",namespaces=NS)) print(root.find("saml:Assertion/saml:Subject/saml:SubjectConfirmation/saml:SubjectConfirmationData",NS).attrib) print(root.find("ds:Signature/ds:SignedInfo/ds:Reference",NS).get("URI"))
web-sp
{'Recipient': 'https://sp.example.test/acs', 'InResponseTo': 'request-7', 'NotOnOrAfter': '2026-11-13T00:05:00Z'}
#response-7
3
sp = SP() print(sp.accept(raw))
accepted:alice
4
print(sp.accept(raw)) print(sorted(sp.seen))
replayed
['assertion-7']
5
print(SP().accept(issue(audience="other-sp"))) print(SP().accept(issue(recipient="https://other.example.test/acs"))) print(SP().accept(issue(request="request-8")))
audience_mismatch
recipient_mismatch
request_mismatch
6
print(SP().accept(raw,now="2026-11-13T00:05:00Z"))
outside_validity
7
tampered = raw.replace(b">alice<",b">mallory<") print(SP().accept(tampered))
signature_invalid

演習完了