Bash と Caddy サーバで ActivityPub のインスタンス(3) ファイルにした POST リクエストの検証

## やること
前回でファイルとして取得した、他のインスタンスからの POST のダイジェストと HTTP署名を検証する。

## ダイジェストの検証
送られてきた POST をファイルにしたものが仮に
1705173013
と名前を付けられて保存されているとする。
このファイルのボディ部分を
```bash
grep -m 1 "{" 1705173013
```
で取得する。もちろん「POST リクエストの空行までを飛ばして、それ以降全てをボディとする」のが正しいが、Mastodon はじめほとんどの ActivityPub インスタンスはボディを1行の json として送ってくるので手抜きし、この方法を取る。何故なら面倒だから。
```
body=$(grep -m 1 "{" 1705173013)
echo $body | head -c -1 | sha256sum | xxd -r -p | base64
```
で返される値がヘッダの
Digest: SHA-256=
以下の値と等しければ良い。

## HTTP署名の検証
ダイジェストの検証もだが、詳しい手順は拙記事(https://qiita.com/tochu-cha/items/1d98c4f527b168b5cdca )を見ていただきたい。ここでは大きく違うところを説明する。
まず、POST してきたアクターの公開鍵を取得しなくてはならない。
- POST ファイル(1705173013)のボディである json の actor タグにある投稿者の URL のフルパスを取得し、
- そこに GET リクエストを送り actor の json 情報を取得し、
- そこにある公開鍵(publicKeyPem)を取得し、
- エスケープを有効にして public というファイルに書き込む。
```bash
curl -s --header "Accept: application/activity+json" $(grep -m1 "{" 1705173013 | jq ".actor" | sed 's/"//g') | jq ".publicKey" | jq ".publicKeyPem" | xargs -I{} echo -e {} > public
```
他は拙記事とほぼ同じだが、気を付けなくてはいけないのはヘッダの
POST
タグの2番目であるリクエストターゲットの値だ。リバースプロキシによって netcat で指定したポート（または UNIX ドメインソケット）に流れて来たリクエストでは
/
だが、これは netcat に流れてきたからで、実際には
/users/aktor/inbox
へ送られてきたものだ。なので
(request-target): post /users/aktor/inbox
がリクエストターゲットとして署名・検証に用いられなければならない。

これらを考慮して書き直したものが以下になる。
```bash:verify.sh
#!/bin/bash

function verify_digest(){
	content=$(grep -m1 "{" <$1 )
	digest=$(grep -m 1 "Digest:" < $1 | cut -d' ' -f 2- | sed 's/SHA-256=//')

	for_verify=$(echo $content | head -c -1 | sha256sum | xxd -r -p | base64) # printf を使うとエスケープ文字を有効にしてしまうので注意
	if [ "$for_verify" == "$digest" ]; then
		echo "Verified OK"
	else
		echo "Failure"
	fi
}

function verify_signature(){

	[ -e low_msg ] && rm low_msg
	while read -r line; do
		tag=$(awk '{print $1}' <<< "$line" | tr  [:upper:] [:lower:] )
		cont=$(cut -d ' ' -f 2- <<< "$line")
		echo "$tag" "$cont" >> low_msg
	done < $1

	curl -s --header "Accept: application/activity+json" $(grep -m1 "{" $1 | jq ".actor" | sed 's/"//g') | jq ".publicKey" | jq ".publicKeyPem" | xargs -I{} echo -e {} > public

	target="(request-target): post /users/aktor/inbox" # 自分への post の検証だから。

	Sig_target=$(grep -m1 "Signature" < $1 | cut -d' ' -f 2-)
	Sig_target=${Sig_target//,/\\n}
	echo -en $Sig_target > sig_target

	. ./sig_target

	echo $signature | openssl enc -A -d -base64 > decode

	for head in $headers; do
		if [ $head = "(request-target)" ]; then
			sig_target+=$(echo "$target\n")
		else
			sig_target+=$(grep -m1 "$head" low_msg)"\n"
		fi
	done

	sig_target=$(echo $sig_target | head -c -3)

	echo -e -n "$sig_target" | openssl dgst -sha256 -verify public -signature ./decode

}

result_digest=$(verify_digest $1)
result_signature=$(verify_signature $1)

if [ "$result_digest" = "Verified OK" ] && [ "$result_signature" = "Verified OK" ]; then
	echo "Verified OK"
else
	echo "NG"
fi
```
```
bash verify.sh 1705173013
```
と引数に目的のファイルを置いて実行する。
成功の場合 verify_digest 関数に "Verified OK" と返させている。 verify_signature 関数も同様に "Verified OK" を返した場合、ダイジェストと署名の両方の検証に成功したということなので、改めて "Verified OK" と返すようにしている。
public はじめ decode sig_target low_msg と割と多くのファイルが残るが、気になるのならば
```
rm public decode sig_target low_msg
```
など最後に入れて消去すればよい。


## 参考記事
[ Bash (and more) で HTTP 署名とその検証 ](https://qiita.com/tochu-cha/items/1d98c4f527b168b5cdca)