LoginSignup
8
5

More than 1 year has passed since last update.

SAML RequestのデコーダをPythonでサクッと書く

Last updated at Posted at 2022-02-19

概要

SSO (Single Sign-On)を実現するための仕組みの1つであるSAMLでは、SP (Service Provider)を起点とする (SP-initiated)場合、SPからIdP (Identity Provider) にSAML Requestを送る必要があります。

この際、SAML RequestはXMLで記述されますが、SPからIdPに渡される際は(特にGETのパラメータとして渡される場合)通常はDeflateで圧縮+Base64エンコードされ、人間の目では解読できなくなってしまいます。

先日、急いで自前のデコーダを用意する必要があり、Pythonで書いたところサクッと書けて便利だったので、小ネタとして投稿します。

背景

障害対応の一環で、SPがIdPに渡しているSAML Requestが正常に生成されているかや、中のデータは正しく設定されているか、などを調査する必要が生じました。
しかしこのSAML Request はDeflateで圧縮され、Base64でエンコードされていたため、そのままでは読むことができず、デコーダを用意する必要がありました。

そこで「SAML Request decode」などでググると、JavaScriptなどを使ってサイト上でデコードしてくれるサービスは沢山ある事は分かりました。

ただ、調査したいSAML Requestは(主にコンプライアンス的な理由で)社外に出すとマズいものでした。
そのため、そういったネット上のツールに頼ることなく、自力でデコーダを用意することにしました。

必要最低限のコード

saml_request_decoder.py
import zlib
import base64

saml_request = ''
print(zlib.decompress(base64.b64decode(saml_request), -zlib.MAX_WBITS))

障害対応という状況の性質上、必要最低限動けば良いコードをなるべく短時間で実装する必要がありました。
その時に実装したコードが上記のものです。

デコードしたいSAML Request1

saml_request = 'fVBNS8NAEP0ry97ziT10SAKhRQioiBUPXmRJpnRhP+LOrNZ/7yZFqJde533Me68hZc0MfeSTe8HPiMTibI0jWIFWxuDAK9IETlkk4BEO/eMD1HkJc/DsR2/kleS2QhFhYO2dFMO+lXpCx/qoMXxUUrxhoAS1MjETThRxcMTKcTqV5V1W1Vm5eS23UFew2b5L0f/Z7byjaDEcMHzpMckmPLeykl2zZILVK3Qn5pmgKGjO8azsbDAfvS2WdHVTXDObyyxPqcCwf/ZGjz+iN8Z/7wIqxlZyiCjFvQ9W8e3Ky0VP2XGlAgflSKfWsuguL/+P3/0C'

のように記述し、実行すると

$ python3 saml_request_decoder.py
b'<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="identifier_1" Version="2.0" IssueInstant="2004-12-05T09:21:59Z" AssertionConsumerServiceIndex="1"><saml:Issuer>https://sp.example.com/SAML2</saml:Issuer><samlp:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/></samlp:AuthnRequest>'

と、人間にも読める形で出力されました。

「ライブラリがないよ」とか怒られる場合

pip3 install zlib

等のコマンドで足りないライブラリをインストールしてください。

たった数行(実際にデコードしているのは最後の1行)で実装できてしまうとは驚きました。
PythonのようなLL (Lightweight Language)はこういう場面でサクッと問題を解決できるので、改めて便利さを思い知りました。

もうちょっと便利にしたコード

緊急時に「とりあえず使える」レベルのコードとしては上記で充分ですが、せっかくなので普通に使えるレベルに改良したいですね。
最低限

  • いちいちPythonコードを編集するのが面倒。コマンドライン引数で渡せた方が便利
  • GETで渡される場合、パラメータはURLエンコードされている。まずそれをデコードする必要がある2
  • (今回は該当しなかったが)POSTで渡される場合、圧縮されずにそのままBase64エンコードされたものが渡されることがある

ぐらいには対応したいです。

という訳で、それらに対応するよう改良しました。

saml_request_decoder.py
import zlib
import base64
import sys
import urllib.parse

saml_request: str = sys.argv[1]
decoded_bytes: bytes = ''
try:
    decoded_bytes = base64.b64decode(saml_request)
except:
    decoded_bytes = base64.b64decode(urllib.parse.unquote(saml_request))

try:
    decoded_bytes = zlib.decompress(decoded_bytes, -zlib.MAX_WBITS)
except:
    pass

print(decoded_bytes.decode())

これで

$ python3 saml_request_decoder.py fVBNS8NAEP0ry97ziT10SAKhRQioiBUPXmRJpnRhP%2BLOrNZ%2F7yZFqJde533Me68hZc0MfeSTe8HPiMTibI0jWIFWxuDAK9IETlkk4BEO%2FeMD1HkJc%2FDsR2%2FkleS2QhFhYO2dFMO%2BlXpCx%2FqoMXxUUrxhoAS1MjETThRxcMTKcTqV5V1W1Vm5eS23UFew2b5L0f%2FZ7byjaDEcMHzpMckmPLeykl2zZILVK3Qn5pmgKGjO8azsbDAfvS2WdHVTXDObyyxPqcCwf%2FZGjz%2BiN8Z%2F7wIqxlZyiCjFvQ9W8e3Ky0VP2XGlAgflSKfWsuguL%2F%2BP3%2F0C

のようにコマンドライン引数としてデコードしたいSAML Requestを渡せば3

<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="identifier_1" Version="2.0" IssueInstant="2004-12-05T09:21:59Z" AssertionConsumerServiceIndex="1"><saml:Issuer>https://sp.example.com/SAML2</saml:Issuer><samlp:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/></samlp:AuthnRequest>

のように出力してくれるようになりました。

もう少しコードを改良できそうな気もします4が、今回はこれぐらい書いておけば充分でしょう。

何かのご参考になれば幸いです。

参考文献

  1. 今回はこちらのサンプルを使用させていただき、それを圧縮+エンコードしたものを使用しています

  2. 今回はブラウザの開発者ツールがURLエンコードをデコードしたものを用意してくれたので、対処不要でした

  3. 上記の例は、よく見るとURLエンコードされたものを渡しています

  4. 例えばもう少し例外処理を丁寧にするとか、XMLを見やすいようにフォーマットして出力するとか

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5