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 / 公開試料と固定時刻
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.testprint(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-7sp = SP()
print(sp.accept(raw))accepted:alice
print(sp.accept(raw))
print(sorted(sp.seen))replayed ['assertion-7']
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
print(SP().accept(raw,now="2026-11-13T00:05:00Z"))outside_validity
tampered = raw.replace(b">alice<",b">mallory<")
print(SP().accept(tampered))signature_invalid
演習完了