0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Bash (and more) で HTTP 署名とその検証

Last updated at Posted at 2024-01-09

HTTP 署名の付加

curl -POST -H "Date: $(date -uR  | head -c -6 | sed -e 's/$/GMT/')" -d '{"foo";""bar}' http://ホニャララ

とすると
http://ホニャララ

POST / HTTP/1.1
Host: ホニャララ
User-Agent: curl/バージョン
Accept: */*
Date: Fri, 05 Jan 2024 10:38:24 GMT
Content-Length: 13

{"foo";""bar}

というリクエストが送られるわけですが、HTTP 署名が求められるエンドポイントだと受け入れられず 401 などが返ってきます。
ここでは例として、内容のダイジェストと日付を SHA-256 秘密鍵で署名したものを Signature タグにしてヘッダに付けて送ると、(秘密鍵から導出された)公開鍵で検証して受け入れるエンドポイントを仮定します。ActivityPub なんですけど。

まず、秘密鍵・公開鍵を作ります。

openssl genrsa 2048 > private.key
cat private.key | openssl rsa -pubout > public.key

で、秘密鍵・private.key 、公開鍵・public.key が作られます。

private.key
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAtv+pCzDqmiLTAWi5c1WcuilPmQ9PrO+tLuYDKuzrMmKzBC6A
2cIa2YGtxH4YQv436PjFi8QroFFjuVtF1AcJfv9+UzV73TIFgIQvst+O3/hlvZXv
ZFzISXj+cusJcrIbYTZEI3685C6AP4O3mLwuskQ8ExG41DwQT3j1kSSJWCnfAUkO
saFA41qOf8OAIVYPquosPxvcHYEyVZvvI2P+ltSF5XF+02Z7PwcoNSAWAFF4iRhH
WmUvGen7WfkVNjlIa+xqKyjYnq4zER0rLHo4FFeMxJ/tTDVcGZVnCU7TjgO1rUFn
Yf74L0fhWnP9pWT8H/dDOAp/0yGU9ODYL5C2CwIDAQABAoIBAAzJMiNxCIM6eam4
inSPf8LWDhSwqC16FYyYT5JZOVms4bsiEKimUj/uOpjnAoTzxC5H622HiFDMPv59
bRSSZUx1R3tC2mOrEg1Xrwl9azsk3N7xMee+P9Q6WvTmjSNxZE5Xf01HlqUOxrEp
X9ORGmYkNFpUu6hAhhc3aVj5x4rcS9GO5Pqs/S9Q20cDLs9S7Ph1h9ZKuiBHi3o/
6uXCDVi/82re64VtyIIfa6GLIZmfXuSiGq4rGV+pjUA6045kkXU6/7VdHysG0yeh
yJDblxXl+ZA2Z7FUaoETMZbuVI79n6VpFvdMtpAVrRqlTBuiMYBj4bSiVRp/lmXT
S9mOKbECgYEA3pxV4cryUYVSsoTs0TF6zcpa7Y9BAGIGdxnJBanVUHn6BPSjwwdS
HyWw/MfgJKM0YCogliro6wkRBBsoo6oSQ9v//Ak4UR2ALV1bXqMtLQN+ZfT0I0mf
ptMz0LTCeeJEhl3jqmf3VWc8WuGYT8j4P8ISQIB03OCsYqCGmMsc2/MCgYEA0nJT
68sCzc5KWx7fhtKwBkIlVvXuZw2gM/MCphGSiCmjws89hscH5VteAqwaXXJN35/u
nhsXbz1anrND2TACkxTYh9RnzlS72WXhp7aiFODSBorLfnAoQC1oUJtU3N4b9Jr3
vfYUDhQ7w0gHTPo04HNJIqrV44eUgrOGJzFaO4kCgYEAoITIpNUroD7r39Bb14i/
TY8hu/U1YOpOUSllu9C4AZzC/TnOsD4iKFWMZupVpPWOOd2Gu+HbLEfQXk6bIlDr
dMU5s+qOEmecWpnb3cT6OWAwj6JvPNbE6Y2X6mG9bhgLY3xmQyVOwuV7LulHZBi9
cXQmpx45pl/XzhKEm76yY6MCgYAw9oiwA7vTRnvmV3iKVdq+mgU2BmJBd9oePBwR
p2UAaS/A05btFMGxi2CEHqbDtySHMx7BimwZZZz/75WJorH2ppL6h2DfkLdkDWBD
NSrg0K4M954A/PdgdzAeEQXdnY/DiYq6l9ZysCJ3fPq13kEPN8N8XWMGXTUgc8Ry
ufjUgQKBgEtmZv4WQCO2WeGkuGe9xSDJmXxhkNdlp0pNCYbtjLLN4yQWIa5xX2Hj
azqGMUPJbYP1b66KSXPQPjs2Fn4QBMkQ1qfipyLl7O1UtLtQqc4ZkfGZk/OyJCYV
O1UkgRpIgRcWtCj2gFmt+PGNNeM3TE+k/BjloX9wHgZKRoqc4VuL
-----END RSA PRIVATE KEY-----
public.key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtv+pCzDqmiLTAWi5c1Wc
uilPmQ9PrO+tLuYDKuzrMmKzBC6A2cIa2YGtxH4YQv436PjFi8QroFFjuVtF1AcJ
fv9+UzV73TIFgIQvst+O3/hlvZXvZFzISXj+cusJcrIbYTZEI3685C6AP4O3mLwu
skQ8ExG41DwQT3j1kSSJWCnfAUkOsaFA41qOf8OAIVYPquosPxvcHYEyVZvvI2P+
ltSF5XF+02Z7PwcoNSAWAFF4iRhHWmUvGen7WfkVNjlIa+xqKyjYnq4zER0rLHo4
FFeMxJ/tTDVcGZVnCU7TjgO1rUFnYf74L0fhWnP9pWT8H/dDOAp/0yGU9ODYL5C2
CwIDAQAB
-----END PUBLIC KEY-----

次に
{"foo";"bar"}
のダイジェストは

echo -n '{"foo";"bar"}' | sha256sum | xxd -r -p | base64

で求まります。echo の -n オプションで '{"foo";"bar"}' の最後に改行を付けないようにするのが大事です。
求まった
PHw9D0DTPKH8GWPj1QRsRdh2ENZSDdlTZvNpNoHn728=

Digest: SHA-256=PHw9D0DTPKH8GWPj1QRsRdh2ENZSDdlTZvNpNoHn728=

と、"SHA-256=" を加えてタグにし、ヘッダに付加します。
Signature タグは

Signature: keyId="public.keyを公開している場所(サイトによって色々)",algorithm="rsa-sha256",headers="date digest",signature="署名"

で良いということにします。
問題は署名なのですが、Signature タグの headers="〜〜" に並んでいるように date digest の順に、頭文字を小文字にして 並べ、間を \n でつないだものを秘密鍵で署名したものです。

Date: Fri, 05 Jan 2024 10:38:24 GMT
Digest: SHA-256=PHw9D0DTPKH8GWPj1QRsRdh2ENZSDdlTZvNpNoHn728=

のままならば

echo -en "date: Fri, 05 Jan 2024 10:38:24 GMT\ndigest: SHA-256=PHw9D0DTPKH8GWPj1QRsRdh2ENZSDdlTZvNpNoHn728="

の結果を秘密鍵で署名したものです。echo に -e オプションを付けることで \n を有効にし、-n オプションで最後に改行を付けないようにします。また、文字列はクォーテーションで括らないと -e オプションが上手く発動しません。

実際にはこの文字列を秘密鍵で署名した上で base64 でエンコードしたものを Signature タグの signature="〜〜" の 〜〜 に加えます。
順にパイプでつないで実行すると

echo -en "date: Fri, 05 Jan 2024 10:38:24 GMT\ndigest: SHA-256=PHw9D0DTPKH8GWPj1QRsRdh2ENZSDdlTZvNpNoHn728=" | openssl dgst -binary -sign private.key -sha256 | openssl enc -A -base64

で、返ってくる値は

SD7/LvW9e0c48M37BGWRPKyQg9bDlPCo5/1xq3BV7eG5vxLd5PhinyeWqtpkiVTuROEn0SlFL4kEJncDchnMiVt5+nwlYefzu/3lXeCMA45jXftoZoid9CV/brKTOAHSvZpnaD2B6vjPo0tpBZ1zerDjFhZqbGWtJdYHJfkA7Slgg16LBPwU5Lqwq6lK8ltyMGHrZ5LoId1AjPb0QwJR7Lv0okexOJPgje9h++2bd8PKLprNsnZXHM6lSPVI8WSkCXu1J5fxB747RHVWE6qw1ZlB99SFxGHKWxK++8BFxxVacQa61bHR89N8PuVo7uF1HVtvr0X+5hKbb9ae5FS/xg==

なので、これをそのまま加えた

Signature: keyId="public.keyを公開している場所(サイトによって色々)",algorithm="rsa-sha256",headers="date digest",signature="SD7/LvW9e0c48M37BGWRPKyQg9bDlPCo5/1xq3BV7eG5vxLd5PhinyeWqtpkiVTuROEn0SlFL4kEJncDchnMiVt5+nwlYefzu/3lXeCMA45jXftoZoid9CV/brKTOAHSvZpnaD2B6vjPo0tpBZ1zerDjFhZqbGWtJdYHJfkA7Slgg16LBPwU5Lqwq6lK8ltyMGHrZ5LoId1AjPb0QwJR7Lv0okexOJPgje9h++2bd8PKLprNsnZXHM6lSPVI8WSkCXu1J5fxB747RHVWE6qw1ZlB99SFxGHKWxK++8BFxxVacQa61bHR89N8PuVo7uF1HVtvr0X+5hKbb9ae5FS/xg=="

が Signature タグです。
そのまま curl にします。ただし、「 -H "" 」 のダブルクォーテーションの中に Sifnature タグ内のダブルクォーテーションが入るので、\ でエスケープします。

curl -POST -H "Date: Fri, 05 Jan 2024 10:38:24 GMT" -H "Digest: SHA-256=PHw9D0DTPKH8GWPj1QRsRdh2ENZSDdlTZvNpNoHn728=" -H "Signature: keyId=\"public.keyを公開している場所(サイトによって色々)\",algorithm=\"rsa-sha256\",headers=\"date digest\",signature=\"SD7/LvW9e0c48M37BGWRPKyQg9bDlPCo5/1xq3BV7eG5vxLd5PhinyeWqtpkiVTuROEn0SlFL4kEJncDchnMiVt5+nwlYefzu/3lXeCMA45jXftoZoid9CV/brKTOAHSvZpnaD2B6vjPo0tpBZ1zerDjFhZqbGWtJdYHJfkA7Slgg16LBPwU5Lqwq6lK8ltyMGHrZ5LoId1AjPb0QwJR7Lv0okexOJPgje9h++2bd8PKLprNsnZXHM6lSPVI8WSkCXu1J5fxB747RHVWE6qw1ZlB99SFxGHKWxK++8BFxxVacQa61bHR89N8PuVo7uF1HVtvr0X+5hKbb9ae5FS/xg==\"" -d '{"foo";""bar}' http://ホニャララ

これで送られる

POST / HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.47.0
Accept: */*
Date: Fri, 05 Jan 2024 10:38:24 GMT
Digest: SHA-256=PHw9D0DTPKH8GWPj1QRsRdh2ENZSDdlTZvNpNoHn728=
Signature: keyId="public.keyを公開している場所(サイトによって色々)",algorithm="rsa-sha256",headers="date digest",signature="SD7/LvW9e0c48M37BGWRPKyQg9bDlPCo5/1xq3BV7eG5vxLd5PhinyeWqtpkiVTuROEn0SlFL4kEJncDchnMiVt5+nwlYefzu/3lXeCMA45jXftoZoid9CV/brKTOAHSvZpnaD2B6vjPo0tpBZ1zerDjFhZqbGWtJdYHJfkA7Slgg16LBPwU5Lqwq6lK8ltyMGHrZ5LoId1AjPb0QwJR7Lv0okexOJPgje9h++2bd8PKLprNsnZXHM6lSPVI8WSkCXu1J5fxB747RHVWE6qw1ZlB99SFxGHKWxK++8BFxxVacQa61bHR89N8PuVo7uF1HVtvr0X+5hKbb9ae5FS/xg=="
Content-Length: 13
Content-Type: application/x-www-form-urlencoded

{"foo";""bar}

が HTTP 署名が付加されたリクエストです。

HTTP 署名の検証

ではこのリクエストを、公開されている public.key で検証します。
まずはリクエストに付加された署名、

SD7/LvW9e0c48M37BGWRPKyQg9bDlPCo5/1xq3BV7eG5vxLd5PhinyeWqtpkiVTuROEn0SlFL4kEJncDchnMiVt5+nwlYefzu/3lXeCMA45jXftoZoid9CV/brKTOAHSvZpnaD2B6vjPo0tpBZ1zerDjFhZqbGWtJdYHJfkA7Slgg16LBPwU5Lqwq6lK8ltyMGHrZ5LoId1AjPb0QwJR7Lv0okexOJPgje9h++2bd8PKLprNsnZXHM6lSPVI8WSkCXu1J5fxB747RHVWE6qw1ZlB99SFxGHKWxK++8BFxxVacQa61bHR89N8PuVo7uF1HVtvr0X+5hKbb9ae5FS/xg==

をファイル signature に貼り付けます。
この署名の値は base64 でエンコードされたものなので、検証の前にデコードして戻します。

cat signature | openssl enc -A -d -base64 > decode

このデコードされた decode を、(http://ホニャララに)送られた(はずの)リクエストのヘッダの Signature タグにある通り、Date タグと Digest タグを、頭文字を小文字にして並べ改行させたもの(echo -en "date: Fri, 05 Jan 2024 10:38:24 GMT\ndigest: SHA-256=PHw9D0DTPKH8GWPj1QRsRdh2ENZSDdlTZvNpNoHn728=")と比べます。

echo -en "date: Fri, 05 Jan 2024 10:38:24 GMT\ndigest: SHA-256=PHw9D0DTPKH8GWPj1QRsRdh2ENZSDdlTZvNpNoHn728=" | openssl dgst -sha256 -verify public.key -signature decode

Verified OK

が出れば検証成功です。

シェルスクリプトにする

これで終わりなら楽なのですが、一つ問題があります。
curl の -H で Date タグの値を指定して送りましたが、この方法だと現実に送る時間とのラグが大きすぎます。たとえば Mastodon のインスタンスではタイムラグが 30 秒以内でないと有効とみなされないそうです。(https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/
なのでシェルスクリプトにして時刻取得・ダイジェストと署名の計算・送信をほぼ同時に行えるようにします。

send_req.sh
#!/bin/bash

post_to=$1 # 第一引数で送り先のアドレスを指定
body=$(cat $2) # 第二引数で送る内容が記されたファイルを指定
date_now=$(date -uR  | head -c -6 | sed -e 's/$/GMT/') # 今の時刻を取得
digest=$(echo -n "$body" | sha256sum | xxd -r -p | base64) # ダイジェストの計算
sig=$(echo -en "date: $date_now\ndigest: SHA-256=$digest" | openssl dgst -binary -sign private.key -sha256 | openssl enc -A -base64) # 署名の計算

curl -POST -H "Date: $date_now" -H "Digest: SHA-256=$digest" -H "Signature: keyId=\"public.keyを公開している場所(サイトによって色々)\",algorithm=\"rsa-sha256\",headers=\"date digest\",signature=\"$sig\"" -d "$body" "$1"
bash send_req.sh http://ホニャララ 送るファイル

で HTTP 署名付きのリクエストが送れます。
send_req.sh の curl のヘッダに -H "Content-Type: application/activity+json" を加えると、Mastodon 等にも通るようになります。

おまけ HTTP 署名の検証用シェルスクリプト

bash
bash verify.sh 検証対象のファイル

で実行。検証対象を署名した秘密鍵から作られた公開鍵を public.key にしておく。

verify.sh
#!/bin/bash

[ -e 4_verify ] && rm 4_verify

while read -r line; do
	[ -z "$line" ] && break
	Tag=$(awk '{print $1}' <<< "$line")
	tag=$(echo ${Tag,,})
	cont=$(cut -d ' ' -f 2- <<< "$line" )
	if [ $tag = "signature:" ]; then cont_sig_comma="$cont"; fi # Signature の値を取る
	echo "$tag" "$cont" >> 4_verify # ヘッダを、タグを小文字にして4_verifyファイルに
done < $1

echo -e ${cont_sig_comma//,/\\n} > cont_sig
# Signature の値をカンマ区切りで改行してcont_sigファイルに

. ./cont_sig # cont_sig ファイルを読み込み

for head in $headers; do
	if [ $head = "(request-target)" ]; then
		target=$(grep -m1 "post" 4_verify | cut -d ' ' -f 2)
		sig_target+=$(echo "(request-target): post $target\n")
	else
		sig_target+=$(grep -m1 "$head" 4_verify)"\n"
	fi
	# headers の値に対応するヘッダのタグの値を \n でつないでいく
	# ただし (request-target) の場合はヘッダの POST の行の2番め(指定ディレクトリ)を取得
done

sig_target=$(echo $sig_target | head -c -3) # sig_target から最後の改行文字と改行を除去

echo $signature | openssl enc -A -d -base64 > decode # signature の値をデコード

echo -en $sig_target | openssl dgst -sha256 -verify public.key -signature decode # 検証

HTTP 署名と検証に Dino Chiesa (https://github.com/DinoChiesa ) 氏にご助言と励ましを頂きました。ありがとうございます。

参考文献

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?