きどたかのブログ

いつか誰かがこのブログからトラブルを解決しますように。

SAML2.0のSP metadataにXML署名するのが面倒臭かった

なにをやったか、備忘録です。正確には記載しませんよ。
無駄なことしてる箇所もあるかもしれない。

<作りたいもの一覧>
XML署名されたSAML2.0のSP metadata XML
②Spring Securityに渡す秘密鍵
③Spring Securityに渡す証明書

<使ったもの>
keytool (jdk付属品、今回openjdk17使ってます)
openssl (linuxだったら使えるでしょ)
xmlsectool (v3.0)

STEP 1: keytoolで鍵ペアを作る (JKS形式キーストア)

注意点は1つ。鍵パスワードとキーストアパスワードを同じものにしておくこと。
ちなみに、このJKS形式のキーストアは、あとでxmlsectoolで使う。

だいたいこんな感じだったかな。
keytool -genkeypair -alias sp-metadata -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -dname "CN=hoge, O=company, L=ota-ku, ST=Tokyo, C=JP" -validity 365 -keystore ./keystore.jks -storetype JKS -storepass password -keypass password

STEP 2: keytoolでJKS形式をPKCS#12形式に変換する

PKCS#12形式は、鍵パスワードとキーストアパスワードが同じという制約があるらしい。
変換後の形式なんだから、変換前のJKSは違ってもいいと思っていたら、

Given final block not properly padded

こういう感じのエラーメッセージがでてきてたと思う。

だいたいこんな感じだったかな
keytool -importkeystore -srckeystore ./keystore.jks -destkeystore ./keystore.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass password -deststorepass password -srcalias sp-metadata -srckeypass password

STEP 3: openssl pkcs12でPKCS#12をいったんPEM形式のキーストアにしとく。

だいたいこんな感じだったかな
openssl pkcs12 -in ./keystore.p12 -out ./keystore.pem -passin pass:password -passout pass:password

STEP 4: openssl pkcs8でPEM形式キーストアを読み込み、秘密鍵をPEM形式で出力。

これは、spring securityに渡すものです。
RelyingPartyRegistrationsBeanDefinitionParserあたりを読み進めれば、pkcs8で読み込んでるのが追えます。

だいたいこんな感じだったかな
openssl pkcs8 -topk8 -inform PEM -outform PEM -in ./keystore.pem -out ./sp-metadata-pkcs8.pem.key -passin pass:password -passout pass:password

うーむ、パスワードついてていいのかな、Spring Securityが開けないのでは? 未確認。

STEP 5: openssl x509でPEM形式キーストアを読み込み、証明書をPEM形式で出力。

これも、spring securityに渡すものです。

だいたいこんな感じだったかな
openssl x509 -in ./keystore.pem -out ./sp-metadata-x509.pem.crt -outform PEM -passin pass:password

STEP 6: PEM形式の証明書をsedとtrでワンラインにしてしまって、Base64エンコーディングされたDER部分を抜きとる。

これは、SP metadata xmlSAML Requestとかで署名するときに検証してもらうための証明書のデータです。
という要素の値としてSTEP 7で突っ込みます。

だいたいこんな感じだったかな
sedでヘッターとフッターを削ったあとに、trで改行コードを削る。
cert_txt=$(cat ./sp-metadata-x509.pem.crt | sed -e "1d" -e "$d" | tr -d "\n")

STEP 7: ある程度作っておいたSP metadata xmlに、証明書データを埋め込む。

これはSTEP 6で作ってる値を、置換しやすいように書いておいた文字列と置換する話です。
sedを使いましょう。しかし注意してください。Base64にはスラッシュも含まれます。
sedを使う際には、スラッシュ以外の区切りでやる必要があります。
これで、XML署名前のSP metadata xmlができました

だいたいこんな感じだったかな
sed -e "s|##TODO##|${cert_txt}|" ./sp-metadata-template.xml > ./sp-metadata-signee.xml

STEP 8: xmlsectoolでXML署名する

だいたいこんな感じだったかな
./xmlsectool.sh --sign --inFile ./sp-metadata-signee.xml --outFile ./sp-metadta-signed.xml --referenceIdAttributeName root --keystore ./keystore.jks --keystoreType JKS --keystorePassword password --keyAlias sp-metadata --keyPassword password

AWS KMSはXML署名なんてできないだろうし、Apache SantuarioでJavaコード書くしかないかなと思っていたが、xmlsectoolがApache Santuarioを裏で使っているようで、安心して使える。

その他の悩み

application.yaml秘密鍵のロケーションを書くのは、あまりセキュアとは思えない。SecretsManagerから取ってくるようにしたいんだけど、そうするとコードを書かないといけないだろう。

証明書の更新は、IdPでもSPでも発生するだろう。use='signing'のKeyDescriptorが2つ書かれたmetadataを交換しないといけないんだろう。メタデータ自身へのXML署名もその時新しいのにするだろう。