Fediverseに参加するための最低条件2

ActivityPubの約束どおりリクエストのキャッチボールができること

公開鍵・秘密鍵を使って署名の生成と認証

    :投げる
  • 秘密鍵を使って署名したHTTPリクエストをFediverseに投げこむ
  • 正しく署名されているか確認のため、自分のアカウント情報のJSONに記載された公開鍵を要求される
    :受けとる
  • 投げこまれた署名付きリクエストを受けとる
  • 正しく署名されているか確認のため、お相手のアカウント情報のJSONに記載された公開鍵を要求する

【秘密鍵と公開鍵の作成】

ActivityPubのActivityリクエストには署名が必須。そのために「秘密鍵」「公開鍵」を作っておく必要がある。この署名というのは、このリクエストは確かにわたしが投げたものです、というのを証明するためのもの。
暗号化の方法はいろいろあるらしいけど、RSAという方式?で暗号化すればいいらしい。
ここ、だいじなんだけど、伏魔殿でわたしのような素人の理解のおよぶところではない。とてもじゃないけど無理。

    結果オーライの理解
  • 「秘密鍵」を使って「対象とする文字列」を暗号化した署名を作成する
  • 「秘密鍵」で暗号化されたものは「公開鍵」と「対象とする文字列」を使って署名が正しいかどうかを認証できる
  • 「秘密鍵」は絶対に漏らしてはいけない

てことで、まずは「秘密鍵」「公開鍵」を作る。
それには、opensslを使うのが手っとり早い。わたしの場合はchromeOSのlinux開発環境。

「秘密鍵」(private.key)の生成


$ openssl genrsa -out private.key 2048
	

2048はbit長というか、言ってみれば強度という理解で大丈夫そう。2048ぐらいで生成するのが良いらしい。

できる秘密鍵は
「-----BEGIN RSA PRIVATE KEY-----XXXXXXX----END RSA PRIVATE KEY-----」
改行されて整形されている。そのまま署名の暗号化に使う。

「公開鍵」(public.key)の生成
最初に作った「秘密鍵」(private.key)を使う。


$ openssl rsa -in private.key -pubout -out public.key
	

できる公開鍵は
「-----BEGIN PUBLIC KEY-----XXXXXXX-----END PUBLIC KEY-----」
改行されて整形されている。

この公開鍵をtype:PersonのJSONの「publicKey」に記載する(FediverseからActivityPubを喋るアカウントとして認識されること)。


"publicKey": {
    "id": "https://YOUR-DOMAIN/USER-NAME#main-key",
    "owner": "https://YOUR-DOMAIN/USER-NAME",
    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\n+Y51TMAWw8+uuuZeru6KyQRgcno1tYGi8ZC+mG3B5OaknRO7mw41qA70DC7r3Xqr\noguRlTc2R2Xes6iPs0/wfPCs7PmUI8NMFEzV+sg4MOcgLQvaJ2mnNBgcNCQshVqo\n-----END PUBLIC KEY-----\n"
  }
	

このJSONに記載する時に、公開鍵の「改行」は「\」(バックスラッシュ)「n」(アルファベットの「n」)の2文字に置換して一行にする。

こちらが送った、署名したリクエストが正しいものかどうか、お相手が認証するために、このtype:PersonのJSONの「公開鍵」を取得するためにアクセスしてくる。

以上で下準備の完了となります。

【署名(Signature)を作る】

署名はHTTPリクエストにつける
投稿やFollowのActivityのリクエスト(POST)に必要で、サーバーによってはPersonのJSON取得のためのリクエスト(GET)にも必要だったりで「このリクエストはわたしが送りました」の証明。

HTTPリクエストのsignature(署名)


keyId="https://expample.com/users/name#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="Nf5TA/8fQP61tUFyyhbEGtrZ309tyFUAhWAotnh1SxSKkhYbFSU8b+074URyonFqpmvSrQmckkD+o2dgRzBgFOt+8jmE1amYc+7BqAYiEHkXmwIl2DUjcodF6Bx0boaIlWq5+cPtQtr2qKndofljxhkdHdIDe/pPeg5dvWky4jhJbdNQXvjLpeopz7SkUuOgaTJokoe/4hh5OyqFwKPuVhknES4AWuPhonS1Tfqqv8fv1F9a4darcOmb630Nrmwb+gr4QbafpWn7Jj8DxerqBfKoYis9yqAbGfar3he66Vm2j2ClADsyBQ8DXcVd6vYdottHkMhXw+gSdoFF0WoOag=="
	

「keyId」「algorithm」「headers」「signature」の4つで構成されている。
(perlやrubyだと環境変数「ENV」で取得できる)

  1. keyId
    signatureの認証に必要な「公開鍵」が記載されているtype:PersonのJSON(アカウント情報)のurl
  2. algorithm
    rsa-sha256。暗号化方式。RSAで暗号化してshaでハッシュ化(既存のモジュールをブラックボックスとして使えるので理解不要。呪文)
  3. headers
    署名の対象
  4. signature
    署名

【署名する対象】

署名の対象はheadersで指定する。

  1. (request-target)
  2. host
  3. date
  4. digest
  5. content-type

ActivityPubの仕様より多いけど、今日時点で飛んでるPOSTリクエストはだいたいこの5つ。
signatureを作る時はこの5つで作れば大丈夫だろう。

1)(request-target)
リクエストメソッド(POST GET) [半角空白] URI
URIはurlから「https://exmple.com」を除いた部分

2)host
ホスト名。urlから「https://」を除いたドメイン部分

3)date
曜日、日月年、時間

4)digest
content(本文)のdigest(ハッシュ)

5)content-type
application/activity+json(決め打ち)

署名の対象

(request-target)post /inbox
hostexample.com
dateWed, 14 Dec 2022 07:28:00 GMT
digestSHA-256=Z2EHR3+Srb66MmKASfnRcbk83F8SBrW1LIiwx15/g80=
content-typeapplication/activity+json

4)digestというのは
送信する内容、本文(content)をsha256でハッシュ化して、それをbase64でエンコードしたもの
perlだと以下


my $digest = 'SHA-256=';
$digest .= encode_base64(sha256($content),"");
	

署名に必要な(署名の対象にする)ものが揃ったらそれを順番通りに
key[: ](←半角のコロンと半角空白)value[改行]
で繋げる。
(※(request-target)やhostはアルファベット小文字にする)


(request-target): post /inbox
host: example.com
date: Wed, 14 Dec 2022 07:28:00 GMT
digest: SHA-256=Z2EHR3+Srb66MmKASfnRcbk83F8SBrW1LIiwx15/g80=
content-type: application/activity+json
	

この文字列を対象にしてPOSTリクエストにつける署名(Signature)を作る。

本文(contents)がないGETリクエストなんかは
(request-target) / host / date
の3つを対象に署名すればOK。

【署名(Signature)を認証する】

飛んでくるリクエストのSignatureで指定されているkeyIdのurlにアクセス、GETリクエストを投げて、お相手のアカウント情報、type:PersonのJSONを取得。
JSONに記載されている公開鍵を使って認証する(署名が正しいかどうか確認する)。

認証の対象にする文字列は、同じくSignatureのheadersで指定された要素を順番そのままで使う。
(署名を作る時に例にあげた5つとは限らない)

headersで指定されているのが

  1. (request-target)
  2. host
  3. date
  4. digest
  5. content-type

だったらリクエストから該当するものをそのまま署名対象とする。
(request-target)だけは、REQUEST_METHODとREQUEST_URIの2つ必要。

飛んでくるリクエスト

REQUEST_METHODPOST
REQUEST_URI/inbox
HTTP_HOSTexample.com
HTTP_DATESun, 20 Oct 2024 21:30:16 GMT
HTTP_DIGESTSHA-256=ETw1NwooStIkE4vUyRfYU8udaBeNl4xR8237uCj2pEc=
CONTENT_TYPEapplication/activity+json

perlやrubyだと環境変数 ENV に設定されるので各々取得して、署名を作る時と同様に整形して署名対象の文字列を作って認証に使う。

以上で、署名の作成・認証となります。

ここ以下は、perlの秘密鍵・公開鍵モジュールをブラックボックスで使っている例です。

使っているperlのモジュール
「Crypt-Perl-0.38」
これはpure perlで書かれているので、perlさえ動けばどのサーバーでも解凍展開するだけで使えます。

依存関係でほかのモジュールも必要になりますが、すべてpure perlなので問題はありません。

【秘密鍵を使って署名する】


my $priv = Crypt::Perl::RSA::Parse::private(PRIVATE-KEY);
my $sign = $priv->sign_RS256(SIGN-KEY);
encode_base64($sign, "");
	

PRIVATE-KEYは秘密鍵
SIGN-KEYは署名対象の文字列

最後の1行が返す文字列がSignature(署名)

【公開鍵を使って署名を認証する】


my $pubkey  = Crypt::Perl::RSA::Parse::public(PUBLIC-KEY);
my $decoded = decode_base64(SIGNATURE);
$pubkey->verify_RS256(SIGN-KEY, $decoded);
	

PUBLIC-KEYは公開鍵
SIGNATUREは署名
SIGN-KEYは署名対象の文字列

最後の1行が1だったら認証OK、そうじゃなかったら認証NG。

[2024-10-30 07:46:11] v1.0.1

[2024-10-25 08:04:03] v1.0.0

Menu