ひまつぶし雑記帖

HTTP Signatureをlolipopレンタルサーバーで作成

2023/8/22 [09:42:59] (火) 天気

ActivityPubでPOSTするには電子署名が必要。
というエントリを書いたけど、WEBでサーバー同士でやりとりしたり、WEBサービスのAPIを利用したり、電子署名が必要なのはActivityPubに限らず。

ちらほら書いてきたように、lolipopレンタルサーバーにはperlで電子署名をする時によく使われるモジュール「Crypt::OpenSSL::RSA」がインストールされていない。
ということは。lolipopではHTTP Signatureを作ることができない「Cryptが使えないんじゃ話にならない」ということになる。

しょうがないんで、ローカルにcpanから「Crypt::OpenSSL::RSA」をインストールして、ActivityPubにPOSTする時はローカルのスクリプトを使う、という急場しのぎ。
これはこれで、投稿するのにひと手間あった方が事故も起こさなくていいかな、と思ってやってたんだけど、やっぱりサーバー上だけでやれるならラクちんなことは確か。

なもんで、グーグル先生にお願いしてみたら
「Crypt-Perl-0.38」https://metacpan.org/pod/Crypt::Perl
↑pure perlのCryptモジュールがあった。

インストール時にコンパイルが必要なモジュールはOSやファイル構成、環境に依存するけど、PurePerlというのはperlだけで作られているので、perlさえあればサーバーにそのままアップロードして使える。

さっそくダウンロードして展開。テストスクリプトで試したところ、依存関係で必要なモジュールがあったのでそいつらもダウンロードして展開。

Class-Accessor-0.51
Bytes-Random-Secure-Tiny-1.011
Convert-ASN1-0.34
Crypt-Format-0.12
(あと、たぶん以前amazonのAPIがらみでインストール済みのpure perlのDigest::SHAも必要)
当然ながら(?)これらのモジュールもpure perlなので、コンパイル不要、展開するだけで使える。

サーバーの適当なところ、例えば「lib」というディレクトリを作って展開したファイル群をアップロードするだけ。
lib/Bytes/
lib/Class/
lib/Convert/
lib/Crypt/ 
スクリプトで use lib 'lib' などとやればlolipopレンタルサーバーでもCrypt RSAが使えるようになる。

いや。ぶっちゃけ。これらモジュールの作者のみなさんには大感謝。大袈裟じゃなく狂喜乱舞小躍りだった。ほんとうにありがとうございます。
マジperl使いすげー。perl話者が減ってるだろう昨今、凄腕のperl使いはワシントン条約で保護すべき存在。

使い方は「 RSAモジュールで公開鍵と秘密鍵 」とほぼ同じ。

秘密鍵を使う
my $privatekey = Crypt::Perl::RSA::Parse::private($private-pem);
公開鍵を使う
my $publickey  = Crypt::Perl::RSA::Parse::public($public-pem);

Signatureを作る。

my $sign = $privatekey->sign_RS256($sign-str);
encode_base64($sign, "");


Signatureを検証する。

my $decoded = decode_base64($sign);
$publickey->verify_RS256($sign-str, $decoded);



あとは。
こうすると、思いつきから投稿放流までが直結するので、事故を起こさない自制心が必要となるです、はい。
image

»電子書籍制作代行についてはこちら

最低限お一人様ActivityPubサーバーにちょい足し

2023/7/28 [13:24:34] (金) 天気

ActivityPubを話すことで、Fediverse連合空間からアカウントとして認識されて、FollwやFollow受付、Noteの投稿ができるようにした。

最低限実装のお一人様サーバーなのでいろいろ手抜き。
フォロワー管理もそのうちのひとつ。followersへのアクセスに対して中身はからっぽで、ステータス200を返すだけでも特に問題はないということだし、何も用意せずただ200 OKを返してた。

実際、アカウントもサーバーも認識されたので、followers.jsonはなくても問題はない。

だけど。
「各 actor は followers コレクションを 持つべきである(SHOULD)」
W3C Recommendation
ということなので、それなりのものを用意しておいた方が良さそう。

mastodonもmisskey系もサーバーで共有のinbox、ビル入口の郵便受けみたいなものを持っていてそこに届いた郵便物をビル内の各人に配送するための仕組みとして、こちらのfollowers.jsonを利用してることが多いらしい。
つまり、followers.jsonを用意しておけば、こちらは相手の入ってるビル宛にだけ投稿をPOSTすればOKということ。

ビルの宛先はフォロワー情報の中のshared_inboxのURL。
ここに送ると受け取ったサーバーは、こちらのフォロワー情報をみて、サーバー内のフォロワーのinboxに配達してくれることになっている。

今のところ、これについてはActivityPubの決まり事ではない。mastodonやmisskeyなどは対応してくれているらしいが、サーバーごとで対応が違うので現物合わせ、というか確認しながら試しながら、実装するのがよさそう。

followersの記述はシンプル。

{"@context": "https://www.w3.org/ns/activitystreams",
"id" : "https://example.com/actor/followers",
"type": "OrderedCollection",
"totalItems": 5,
"orderedItems": ["https://hoo.jp/users/foo",〜]}


id,type(OrderdCollectionがMUST)、フォロワーの数、フォロワーのURLの配列。
これだけなので、フォロワーが1000人10000人もいるならともかく、10人程度なら手作業で作って用意してもいいんじゃないかな。

以上の元ネタは
https://fedibird.com/ の管理人、のえるさん(@noellabo@fedibird.com)情報

送る相手の情報に、inboxの他にshared_inboxがある場合があります。というか、MastodonやMisskeyなら必ずあります。

同じサーバに所属するフォロワーは同じshared_inboxになるので、これでまとめると、一つのサーバに対しては一回だけPOSTすれば良くなります。これによりかなり効率化されています。

宛先自体は、ToないしCcに送り元アカウントのfollowersコレクションを指定すれば、受け取ったサーバ側でそれぞれに配送してくれます。

shared_inboxがない場合は、それぞれのinboxにPOSTします。

いずれの場合も、同じIDのCreate、同じIDのNoteであれば、受け側が重複排除するので、何度も送っても大丈夫です。


フォロワーの数だけリクエストを飛ばす=マルチポストの迷惑サイトになったらまずいなあ、と心配だったのでfedibirdでそのことを投稿したら返事をいただけたもの。ありがとうございました!

image
AIに郵便配達を描いてもらったんだけど、なかなかうまく意図が伝わらず、かろうじてこの1枚。

»電子書籍制作代行についてはこちら

ActivityPubにおそるおそる話しかけてみた

2023/7/26 [12:37:55] (水) 天気

ActivityPubを通じてフォローしたり投稿したりするには避けて通れないRSAの署名、というかサインというか。
これ絡みが、もう本当に五里霧中で迷走するばかり。
いろいろ検索しまくってこれでイケるかも?ぐらいのところまで来たので、いったんメモ。

RSAモジュールで公開鍵と秘密鍵
↑鍵を作ってサインする方法は前回のこれで正解。

一番の問題は、サインを作る対象となる文字列をどうするの、というところ。これはなんでもいいんだけど、ActivityPubに通すので、そっちに合わせる必要がある。

結論からいうと必要な材料は以下。
・リクエストのメソッド(POSTとかGET)とリクエストするURI
・相手側のホスト名
・日付、
・本文のDigest
mastodonの仕様だと必須なのは、日付とdigestの2つなんだけど、実際にmastodonから飛んでくるリクエストのログを見ると、この4つが必ず入っている。
オマケでcontent-typeやacceptを入れることもある、らしい。

具体例をあげると

(request-target) POST /actor/inbox
host: example.com
date: Wed, 26 Jul 2023 02:00:32 GMT
digest: SHA-256=unkTWZOjiNdJT4SDuAU6tCy6A0QM0PZ2bs0353xOt+k=
content-type: application/activity+json


「キー 区切り 値」の並び
host「:」「半角空白」example.com
という形式で、キーはすべてアルファベットの小文字。
(request-target)だけはコロンは不要

これら、ひとつひとつを「改行」で繋げて並べた文字列を対象としてサインすることになる。

digestというのは

元データから、ハッシュ関数と呼ばれるあらかじめ定められた計算手順により求められた値です。 ダイジェスト値とも呼ばれます。 ハッシュ値を求めるハッシュ関数(※)には、元データが1ビットでも異なれば大きく異なるハッシュ値が生成される(同じハッシュ値になることが実用上ない)方式が選ばれます。


perlだと「MIME::Base64」のencode_base64という関数をそのまま利用。
encode_base64(本文,"")と、2番めの引数に空を渡さないとDigestに改行がついてくるので注意。
本文というのはfollowやcreateといったActivityPubで使うjson文字列。

対象文字列が決まったら、RSAで署名して、リクエストのヘッダに入れる。

'host' => 'example.com',
'date' => 'Wed, 26 Jul 2023 03:52:07 GMT',
'digest' => 'SHA-256=unkTWZOjiNdJT4SDuAU6tCy6A0QM0PZ2bs0353xOt+k=',
'content-type' => 'application/activity+json',
'signature' => 'keyId="https://tokoroten.doncha.net/t2aki#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="qCEXfTo~略~I9zfRqcUaTwjew=="',


↑これはperlのlwpを使ってヘッダに設定している部分。
Signatureの
keyIdは「actor.json」に入れた公開鍵のkeyId
algorithmは暗号化の方法「rsa-sha256」

↓適宜改行は入れたけど、こんなリクエストを飛ばす

HTTP_DATE :: Tue, 25 Jul 2023 23:55:38 GMT
HTTP_DIGEST :: SHA-256=giwfKVIT61lc9LpR7EsgEJBmHilheDpPwFnqdjMBjrA=
HTTP_HOST :: example.com
HTTP_SIGNATURE :: keyId="https://tokoroten.doncha.net/t2aki#main-key",
algorithm="rsa-sha256",
headers="(request-target) host date digest content-type",
signature="SE〜略〜JEVPj9


サインの検証は問題ないし、リクエストもこれで問題ない、と思ったんだけど、mastodonだと401で弾かれる。firefishだと202でどうやら受け付けてもらえるっぽい。

同じリクエストを送ってるのになんでこっちはダメでこっちがOKなのか理解できていないのが現状。


その他ハマったところ。

公開鍵って改行されてるけど、actor.jsonのpublicKeyPemに入れるのにどーすんの問題。
jsonは改行入りデータは扱えない。ただただ改行を削除して一行にすればいいんだろ、ぐらいに思ってたんだけど、検索して眺めてると「バックスラッシュ」「n」という2文字の文字列になってるっぽい。これ、改行なのか2文字の文字列のことなのかわけわからず、ほぼ1日弱経過。
perlのJSONでencode_jsonしてみりゃいいことに気づいてやってみたら、2文字の文字列に置換されていた…改行を「バックスラッシュ」「n」の2文字の文字列て、なんか不細工じゃね。

(request-target)ってなんやねん問題。
どのリクエストのヘッダもこれが先頭にあったんで、てっきり、これに続く要素を要求しますよ、という宣言みたいなもんだと思ってたら大きな間違い。こいつもキーのひとつだった。
HTTPメッセージに署名をするSignatureヘッダの標準化

Digestの生成を疑った問題。
リクエストが401で弾かれっぱなしだったんで、どこが問題だろうと。HOSTやDateは目で見てわかる文字列で、そこは大丈夫だったので、Digestが違ってるわけ?と。
perlのDigest::SHA qw(sha256) MIME::Base64 encode_base64(sha256($content),"")で作ったDigestの検証。
コマンドラインで
cat accept.json | head -c -1 | sha256sum | xxd -r -p | base64
などとやって差分のないことを確認。Digestも問題なかった、やっぱり1日弱経過。


実際、まだマストドンからは401で弾かれるので、何か間違えてるはず。
とはいえ、 himagine.club (firefish)から202が返ってきたを見てめっちゃ嬉しかったので、ここらでひと段落とするかなあ。これ以上やったらただでさえマダラハゲなのに禿頭一直線になりそうで怖い。

mastodonのSignatureのソースコード
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
ActivityPubに対するSignatureのPHPのソースコード
'(request-target) %s %s%s'

あれ?(request-target)の後ろにコロンがいるのかいらないのかよくわからないぞ。コロン一つでverifyの結果が違う…て、どっちでやっても401だったけどな。
[07/27 19:52:17] うまく開通したスクリプトではコロンをつけた文字列。

[07/26 16:29:44] 追記
をっと。202が返ってきても繋がったわけじゃなかった。まだどことも繋がらないってこと。どこが違うんだろ。401じゃないってことはヘッダやサインは問題ない、という理解でいいのかな。これは根が深い。お手上げ、かなあ。

[07/27 11:21:59] 追記
開通!というかActivityPubを通じてFediverse連合空間に入ってfollowもpostもできた!

401の原因。
actor.jsonに記載のpublicKeyPemの始まりのハイフンがコピペミスでひとつ足りなかった。5つ必要なのに4つしかなかった…だけだった。
我ながら雑で阿呆なミスに愕然し、驚愕するばかり。

とはいえ、この何日か、けっこう必死こいて調べまくったので、SignatureやHTTPヘッダについて改めて知ったこともあったので、結果オーライ。

副産物というかverifyするスクリプトやログを片っ端から取るスクリプトを作ったのも今後役に立つだろうと思うし。

image

»電子書籍制作代行についてはこちら

RSAモジュールで公開鍵と秘密鍵

2023/7/22 [10:09:31] (土) 天気

ActivityPubでPOSTするには電子署名が必要。
でも、こいつが簡単じゃない…それどころか、めっちゃわけわかめ。

今回のゴールは以下の3点。
秘密鍵公開鍵の生成
秘密鍵を使って対象文字列の署名、サインの生成
公開鍵を使ってサインの検証

その1)
秘密鍵と公開鍵の生成はopensslコマンドを使うのが手っ取り早い。

秘密鍵の生成
openssl genrsa -out private.key 2048
2048はbit長というか、言ってみれば強度という理解で大丈夫そう。2048ぐらいで生成するのが良いらしい。

できる秘密鍵は
-----BEGIN RSA PRIVATE KEY-----XXXXXXX----END RSA PRIVATE KEY-----
改行されて?整形されてるんだけど、このまま使うっぽい。

公開鍵の生成
openssl rsa -in private.key -pubout -out public.key
最初に作った秘密鍵を使って公開鍵を作る。

できる公開鍵は
-----BEGIN PUBLIC KEY-----XXXXXXX-----END PUBLIC KEY-----
改行されて?整形されてるっぽいんだけど、このまま使うっぽい。
とはいえ、jsonは改行不可なので、jsonに入れる場合は、改行を削除して一行にしたものを使う、のかな。
[07/26 12:05:16]追記。
改行は「\n」(バックスラッシュとn)に変換する…

その2)
秘密鍵ができたので、perlのCrypt::OpenSSL::RSAモジュールを使って署名。
lolipopにはないけど、ローカルにはcpanでインストールしたので使える。

Fediverse入門―非中央集権型SNSサーバを作ろう!(2)
ここに掲載されているsign.plをありがたくそのまま使わせていただくことした。

秘密鍵を使ってインスタンス生成。
my $key = Crypt::OpenSSL::RSA->new_private_key($pk);
↑$pkという秘密鍵の文字列を渡して作る。

インスタンス生成ってなんのこっちゃ?だけど、呪文の始まり、ことでOK。
さらに、sha256でハッシュ化しますよ、という呪文を追加。
$key->use_sha256_hash();

ここまでやったら、秘密鍵を使った署名ができる。
my $s = $key->sign(INPUT);
signという、そのままのモジュールにINPUT、署名対象の文字列を渡せばRSA署名の出来上がりとなる。

署名対象の文字列とは。
ここでは、HTTPSヘッダのSignatureに使うことを想定して
date:[半角空白]日付文字列
digest:[半角空白]sha-256=本文のハッシュ文字列
みたいなものを「改行」で繋いだもの。

その3)
で、このサインが本当に合ってるのか署名の検証をする。
検証に必要なのは「公開鍵」「サイン」「署名対象文字列」

今度は公開鍵を使ってインスタンスを生成する。
my $rsa = Crypt::OpenSSL::RSA->new_public_key($pubk);
↑$pubkという公開鍵文字列の文字列を渡してやる。

秘密鍵を使った署名と同じ呪文を公開鍵で作ったインタンスに対しても追加。
$rsa->use_sha256_hash();

ここまでやったら検証ができる
$rsa->verify(TARGET, SIGN)
対象文字列と署名が合っていたら1が返ってくる。
この対象に対してこのサインがされてますよ、の証明になる、という理解で大丈夫かな。

サインはおそらくほとんどの場合、base64でハッシュ化する・されているので、
MIME::Base64 モジュールを使って
encode_base64 decode_base64
が必要になってくる、はず。


lolipopだとRSAが使えない、perlのCrypt::OpenSSL::RSAモジュールが入っていないので、あまり頑張りどころでもないよな、と思いつつ、うまくいかないのはなんか、好かん。

そして、意図通りに署名できたので、肝心要一丁目一番地のActivityPubでFollow、AcceptへのPOSTをしてみたんだけど、401で弾かれる。なんでや。

最初のactor.jsonを作った時に、公開鍵はとりあえず、空でもテキトーな文字でもいいというので、ほんとテキトーな文字を入れたのが失敗だったかなあ。
今回作った公開鍵に入れ換えたけど、最初のデタラメ公開鍵が伝播して使われてるという可能性もあったりして。

[08/18 12:30:06]追記
単なるPEMのコピペミスが原因。
サインする、検証するやりかたは上記エントリ通りで間違いなかった。

image 
全然関係ないけど、AIにサイバーパンクな最後の晩餐を描いてもらったら、矮人の宴という半村良的な絵面が出てきたぞ

»電子書籍制作代行についてはこちら

ActivityPubでoutboxの実装

2023/7/18 [10:11:56] (火) 天気

ActivityPubを通じて、Fediverse連合空間にユーザーアカウントとして認識してもらえるようになった。

次はウチのつぶやきというか投稿を拾ってもらえる、たとえばMastodonでtokorotenのコンテンツを表示してもらえるのが目標。

そのためには適切なoutboxを用意する必要がある。
よくわかってないけど、outboxてのは、これまでの行動、投稿のログ、という理解で大丈夫かな。outboxのtypeはOrderedCollectionがMUSTとされていて、たぶん順番も見られるんだろう。

あ。今さらだけど、わたしはど素人なので、見様見真似。間違った情報を垂れ流すことが多くてすみません。先に謝っておきます。

/actor/outbox
へのアクセスに対してこれまで投稿履歴を返す。
create(投稿)したnote(発言)とそれらを取得するためのurlなどの情報が羅列されたjson。
image 
こんな感じだろうと思われる(大雑把)
「投稿」を「投稿しました」で包んで、そいつを順番に並べたものがoutboxという箱になる。

実際に配置したのが以下のoutbox。
といっても、お約束、決まり事通りに並べて作ってるだけなので、手数はともかく、それほど難しいものではない。

{"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://tokoroten.doncha.net/t2aki/outbox",
"summary": "outbox of t2aki@tokoroten.doncha.net",
"type": "OrderedCollection",
"totalItems": 2,
"orderedItems": [{
"type": "Create",
"id": "https://tokoroten.doncha.net/t2aki/activity/00054-20230717",
"url": "https://tokoroten.doncha.net/t2aki/activity/00054-20230717",
"published": "2023-07-17T12:45:08Z",
"actor": "https://tokoroten.doncha.net/t2aki",
"to": ["https://www.w3.org/ns/activitystreams#Public", "https://tokoroten.doncha.net/t2aki/followers"],
"object": {
"type": "Note",
"id": "https://tokoroten.doncha.net/t2aki/items/00054-20230717",
"url": "https://tokoroten.doncha.net/t2aki/items/00054-20230717",
"published": "2023-07-17T12:45:08Z",
"to": ["https://www.w3.org/ns/activitystreams#Public", "htps://tokoroten.doncha.net/t2aki/followers"],
"attributedTo": "https://tokoroten.doncha.net/t2aki",
"content": "[2023/07/17 12:45]\u003cbr /\u003elog取得のサブルーチンはpostとgetでちょっといじらないとダメやな。帰ったら仕込む"
}
}, {
"type": "Create",
"id": "https://tokoroten.doncha.net/t2aki/activity/00053-20230717",
"url": "https://tokoroten.doncha.net/t2aki/activity/00053-20230717",
"published": "2023-07-17T11:37:47Z",
"actor": "https://tokoroten.doncha.net/t2aki",
"to": ["https://www.w3.org/ns/activitystreams#Public", "https://tokoroten.doncha.net/t2aki/followers"],
"object": {
"type": "Note",
"id": "https://tokoroten.doncha.net/t2aki/items/00053-20230717",
"url": "https://tokoroten.doncha.net/t2aki/items/00053-20230717",
"published": "2023-07-17T11:37:47Z",
"to": ["https://www.w3.org/ns/activitystreams#Public", "https://tokoroten.doncha.net/t2aki/followers"],
"attributedTo": "https://tokoroten.doncha.net/t2aki",
"content": "[2023/07/17 11:37]\u003cbr /\u003e他のアカウントのoutboxをみて、そこにあるnoteのURLを叩くとHTMLページが開くんだけど、なんでや。jsonが返ってくるんじゃなかったっけ。HTTPのヘッダを覗いてもjsonじゃなくてフツーのHTMLを返してるぞ。こちらのリクエストに応じてjsonをHTMLに変換してたりするのかな。謎だらけ。"
}
}]}


typeがNoteとなっているブロックが投稿内容で、このNoteが基本となっている。
idとurlは投稿内容へのリンク。このNoteのjsonを返す。
publishedは日付。JSTだから+9とか必要かもしれないけど、とりあえず。
attributedToは投稿者。
toはお約束事。

typeがCreateのブロックがNoteのブロックをobjectに包んでいる。
idとurlのリンクはこのCreateのjsonを返す。今日時点、返すのがこのブロックでいいのか謎。とりあえず。
各要素はNoteと同じなので以下略。

そのCreateブロックを順番に並べてoutboxの完成。

前回設定したwebfingerとactor.jsonで、マストドンで「t2aki@tokoroten.doncha.net」を検索するとユーザーアカウントとして認識される。
image
「他のサーバーの以前の投稿は表示されません」と記載されている状態。

今回outboxを設定したので、個々のNoteのリンクをマストドンで検索するとtokorotenの投稿がマストドンで表示される。
image

検索して一度表示されると、ユーザーアカウント表示の下にも表示されるようになった。
image
たぶん、検索して結果が返ってきたことで、このマストドンサーバーにキャッシュされたのかな。

ただ、やはり謎は深くて。

謎その1)
ユーザーアカウント画面の下の「リモートで表示」というリンクを開くと、わたしのactor.jsonが表示される。ほかのひとの「リモートで表示」を開くとマストドンで開いたり、やっぱりactor.jsonが返ってきたりいろいろ。
リモートで表示とは、マストドンの別サーバーだったら意図通りに開くけど、マストドンじゃないサーバーだったらjsonをそのまま開くということになってるんだろか。

謎その2)
mastodonやmisskeyのoutboxはNoteを包んだCreateブロックを持たずにfirstやlastといったリンクを返すだけだったりするんだけど、これはありなのか。

ActivityPub w3c.org
↑ここがActivityPubの本家本元(?)
頑張って読むしかないんだろうけど、爺さんにはつらいぞ、英語。

そして、やはりRSAで署名したPOSTをどこかに投げないことには始まらない。
これについてはレンタルサーバーにモジュールがないのでほぼ諦め。ローカルで作ってスクリプトで投げる、ぐらいしか思いつかないしなあ。

とはいえ、ごそごそスクリプトを考えるのは爺さんのボケ防止にちょうどいいパズルゲームでもあるなあ。

ちなみに。元ネタの「 ところてんx10 」はほぼ毎日たわごと並べてるので、RSSリーダーにでも登録していただけると更新をお届けできます。


すげー今さらのRSSとか!

[07/19 11:17:32]
謎その1は解決。
HTTP_ACCEPTを見て判別。jsonを要求されてたらjson、そうじゃなければHTMLを返してた。さっそくウチも導入。やっぱ生のjsonが返ってくるとびっくりするからなあ。

https://gihyo.jp/dev/serial/01/perl-hackers-hub/005902

AS2以外の形式の情報を返すこと(コンテントネゴシエーション)も認められています。これを利用して、同じURLでも、ActivityPubの情報を要求された場合はAS2形式の情報を、それ以外の場合はHTML形式での情報を返すようにしています。



[08/04 10:40:12]
publishedの日付問題。
飛んでくるPOSTというかjsonを見ると、JSTだけど+900やJSTという表記がなくて標準時になってる。
localtimeじゃなくてgmtimeを使えば良かった

»電子書籍制作代行についてはこちら

ActivityPubを使ってFediverseにたどり着く

2023/7/12 [02:17:04] (水) 天気

いや、なんのこっちゃだけど。
2023年、ツイッター亡き後の世界となった。ネット空間には祖国を奪われた難民たちで溢れかえり…というB級サイバーパンクSFの設定。

そこで再評価されることになった、実は古より存在するFediverseというネットの連合空間。ActivityPubという共通の言語で繋がるネット世界、ということになった。

てことで、「いっちょかみ」できるかなあ、とここ数日あれこれ検索しまくって試して、とりあえずmastdonで検索して仲間として検索結果に出るところまではたどり着いた。
image 
最小限のActivityPub、いわゆるお一人様で、もちろん喋ることもフォローなどアクションをすることもできない。ただ、連合空間に顔を出した、だけなんだけど。

「mastodonなどに認識してもらえる」のがまずはゴールだったんで、ここまでやったことをメモ。
(すべてはネットで検索して得られたもので、先人たちに大感謝)

ActivityPubに参加するため用意するもの
サーバーやドメインをもっていることが前提。わたしはいつものlolipopサーバー。

どうやら以下の4つがあればActivityPubに顔を出すことはできる、っぽい。

nodeinfo(json)
サーバーの情報、でいいのかな。ここにGETでアクセスされて、ウチはactivitypubやってますよ、ユーザーは1人ですけどね、というのを返す。

{
"openRegistrations": false,
"protocols": [
"activitypub"
],
"software": {
"name": "tokoroten",
"version": "0.1.0"
},
"usage": {
"users": {
"total": 1
}
},
"version": "2.1"
}



host-meta(xml)
webfingerのURLを返す。uriは検索対象のユーザー名の文字列に置換される。

<?xml version="1.0"?>
<XRD xmlns="https://docs.oasis-open.org/ns/xri/xrd-1.0">
<Link rel="lrdd" type="application/xrd+xml" template="https://tokoroten.doncaha.net/.well-known/webfinger?resource={uri}" />
</XRD>



webfinger(json)
host-metaで得られたwebfingerのURLにGETでアクセスされるのでユーザー情報のエンドポイントを返す。

{
"subject": "acct:t2aki@tokoroten.doncha.net",
"links": [
{
"rel": "self",
"type": "application/activity+json",
"href": "https://tokoroten.doncha.net/t2aki"
}
]
}



actor(json)
webfingerで取得したユーザーのURLにアクセスするとこの情報を返す。

{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"id": "https://tokoroten.doncha.net/t2aki",
"type": "Person",
"url": "https://tokoroten.doncha.net/t2aki",
"inbox": "https://tokoroten.doncha.net/t2aki/inbox",
"outbox": "https://tokoroten.doncha.net/t2aki/outbox",
"followers": "https://tokoroten.doncha.net/t2aki/followers",
"following": "https://tokoroten.doncha.net/t2aki/following",
"name": "t2aki",
"preferredUsername": "t2aki",
"summary": "@t2aki@tokoroten.doncha.net",
"icon": {
"type": "Image",
"mediaType": "image/jpeg",
"url": "https://tokoroten.doncha.net/images/prof-image.jpg"
},
"pubKicKey": {
"id": "https://tokoroten.doncha.net/t2aki#main-key",
"owner": "https://tokoroten.doncha.net/t2aki",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\n略==\n-----END PUBLIC KEY-----",
"type": "Key"
}
}



nodeinfoやhost-metaはわかってないまま設置。
たぶん、webfingerでユーザー情報のURLを返して、そのURLにアクセスするとユーザー情報を得ることができますよ、というやりとりがあればまずは第一関門突破だと思われる。

HTTPでリクエストの投げ合いをして相互理解を深める。WEBの基本。

と、わかったようなことを書いたけど、ここにいたるまでハマりまくった。

サイトの構成をどうすればいいのか、そもそもそこから。
結論。考える必要はまったくなかった。
ActivitiyPubはディレクトリ構造とか拘束しないというか全然関係ない。全部jsonやxmlの設定ファイルでリクエストに返答できればいいだけだった。

アクセスされて、ActivityPubを構成するファイルを返す時のヘッダは以下がMUST

Content-Type: application/activity+json; charset=utf-8


てことは動的にヘッダをつけて返答する必要があるのか、ということで、スクリプトで対応。そのために.htaccessのrewriteruleで振り分けることにした。※1

.htaccess

RewriteEngine On
RewriteCond %{QUERY_STRING} ^resource=(.*)$
RewriteRule webfinger.* script-finger%1 [L]
RewriteRule nodeinfo/2\.1$ script-nodeinfo [L]
RewriteRule host-meta$ script-hostmeta [L]
RewriteRule t2aki$ script-actor [L]


ここでのハマり。
apacheのrewriteruleはそのままだと「クエリを含まない」
fingerはresourceというクエリをつけてやってくる。そのクエリが分からないんじゃ何を求められてるのかわからない。それを解決するにはRewriteCondでクエリを判定させてやれば良い、てことにたどり着くのに2時間弱。

この先、フォローしたり投稿したりするには公開鍵秘密鍵で署名する必要があって、わたしの脳みそで理解できるレベルではない。そもそもlolipopではCrypt::OpenSSL::RSAのモジュールが使えないのでないものねだり。

手元、ローカルで作って手作業で鍵を作ってリクエストを投げる、ということを考えるしかなさそう。それもHTTPのヘッダ構成を調べる必要があって、高いハードルを見上げすぎて首を痛めるレベル。
とはいえ、せっかくだし、もう少し頑張ってみるかなあ。

※1
検索していてビックリしたのが静的ファイルだけでActivityPub実装してるかたいらっしゃった。
NetlifyとSupabaseでほぼ静的なActivityPubサーバ

今回まででとても参考になったサイト
Fediverse入門―非中央集権型SNSサーバを作ろう!
田舎の昼のサイレンbotをActivityPubで実装する(マストドンにアカウントを認識してもらう編)
ActivityPubの実装についてのメモ
本当に助かりました。多謝!!!

さっそく、ひとり言掲示板 「ところてんx10」 が役に立った、ぞ。

»電子書籍制作代行についてはこちら

profile

profile

 
doncha.net
contact:
»運営者
@t2aki@tokoroten.doncha.net

ところてんx5

2024/4/25 00:49

だめだ。PDFをWordで開いたらレイアウトが微妙にくずれて行数が正しくない。かえって間違いのもとだ。

2024/4/24 13:43

pdfをWordで開いてみる手もあるか。空行、1ページの行数とか、pdfのまんまだったら行数表示で問題解決。だけど、実際どうだろ。帰ったら試す

2024/4/24 12:58

空行は目視しか確認方法がない…
windowsで半透明にするフリーソフトを見つけたので、各ページの幅で検知する。
全ページの行数を数えてたんじゃ絶対漏らす。そんな集中力はないわ

検索
<<2024/4>>
 123456
78910111213
14151617181920
21222324252627
282930

リンク

WINDOWS版サウンドノベル
おかえりください PC WINDOWS版サウンドノベル
『おかえりください』体験版

[21 Page]« »
1 2 3 4 5 6 7 8 9 10

TOTAL:2982

2024 (5)
1 (2)
2 (2)
3 (1)
2023 (53)
1 (1)
2 (5)
3 (1)
4 (1)
5 (3)
6 (9)
7 (9)
8 (6)
9 (5)
10 (3)
11 (2)
12 (8)
2022 (16)
1 (1)
3 (2)
6 (2)
7 (1)
8 (4)
9 (2)
10 (1)
11 (2)
12 (1)
2021 (12)
1 (3)
2 (1)
6 (1)
8 (2)
9 (1)
10 (1)
11 (2)
12 (1)
2020 (18)
1 (2)
2 (6)
4 (1)
6 (1)
7 (2)
8 (2)
12 (4)
2019 (17)
1 (3)
2 (4)
3 (2)
4 (2)
5 (1)
6 (1)
8 (1)
10 (1)
12 (2)
2018 (21)
1 (3)
2 (2)
3 (2)
4 (1)
5 (1)
6 (6)
8 (1)
9 (1)
10 (2)
12 (2)
2017 (32)
1 (2)
2 (1)
4 (2)
5 (1)
6 (6)
7 (3)
8 (5)
9 (3)
10 (2)
11 (2)
12 (5)
2016 (41)
1 (5)
2 (5)
3 (2)
4 (3)
5 (4)
6 (6)
7 (2)
8 (2)
9 (3)
10 (1)
11 (4)
12 (4)
2015 (99)
1 (11)
2 (12)
3 (9)
4 (6)
5 (8)
6 (8)
7 (3)
8 (5)
9 (16)
10 (6)
11 (1)
12 (14)
2014 (112)
1 (16)
2 (5)
3 (6)
4 (12)
5 (16)
6 (19)
7 (9)
8 (6)
9 (4)
10 (8)
11 (6)
12 (5)
2013 (145)
1 (24)
2 (15)
3 (18)
4 (23)
5 (14)
6 (11)
7 (7)
8 (11)
9 (5)
10 (4)
11 (6)
12 (7)
2012 (103)
1 (1)
2 (1)
3 (4)
4 (3)
5 (7)
6 (26)
7 (17)
8 (5)
9 (8)
10 (10)
11 (11)
12 (10)
2011 (54)
1 (4)
3 (7)
4 (4)
5 (14)
6 (6)
7 (3)
8 (3)
9 (1)
10 (4)
11 (2)
12 (6)
2010 (70)
1 (12)
2 (7)
3 (6)
4 (6)
5 (3)
6 (10)
7 (6)
8 (4)
9 (3)
10 (4)
11 (3)
12 (6)
2009 (144)
1 (15)
2 (12)
3 (12)
4 (6)
5 (15)
6 (6)
7 (10)
8 (9)
9 (17)
10 (12)
11 (14)
12 (16)
2008 (148)
1 (10)
2 (6)
3 (10)
4 (11)
5 (13)
6 (10)
7 (13)
8 (19)
9 (18)
10 (12)
11 (13)
12 (13)
2007 (106)
1 (7)
2 (5)
3 (3)
4 (7)
5 (5)
6 (9)
7 (8)
8 (13)
9 (18)
10 (11)
11 (8)
12 (12)
2006 (158)
1 (28)
2 (28)
3 (25)
4 (7)
5 (9)
6 (7)
7 (12)
8 (13)
9 (10)
10 (7)
11 (6)
12 (6)
2005 (350)
1 (31)
2 (26)
3 (26)
4 (27)
5 (29)
6 (30)
7 (32)
8 (30)
9 (30)
10 (32)
11 (29)
12 (28)
2004 (292)
1 (24)
2 (24)
3 (29)
4 (27)
5 (28)
6 (25)
7 (26)
8 (24)
9 (12)
10 (19)
11 (26)
12 (28)
2003 (318)
1 (22)
2 (25)
3 (21)
4 (28)
5 (28)
6 (28)
7 (28)
8 (29)
9 (26)
10 (29)
11 (28)
12 (26)
2002 (317)
1 (29)
2 (26)
3 (26)
4 (25)
5 (28)
6 (30)
7 (27)
8 (21)
9 (25)
10 (27)
11 (28)
12 (25)
2001 (277)
1 (17)
2 (21)
3 (23)
4 (20)
5 (31)
6 (18)
7 (26)
8 (25)
9 (29)
10 (19)
11 (24)
12 (24)
2000 (53)
6 (9)
7 (4)
8 (2)
9 (3)
10 (1)
11 (15)
12 (19)
1999 (3)
7 (1)
10 (2)
1998 (18)
9 (9)
10 (7)
11 (2)