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

リアルタイムWEB

2023/7/23 [10:38:25] (日) 天気

ツイッターのサービス開始が2006年ということだから、その頃に話題になった言葉が「リアルタイムWEB」。2010年頃の「アラブの春」は確かFacebookの拡散力があったからとかないとか。


拡散力もそうだけど、その「リアルタイム」、文字通り「即時性」というか「刹那的」なのがわたし的ポイントだった、かな。

時系列で流される投稿は目の前のタイムライン、フィードからすぐに流れて雲散霧消。自分の投稿も他人の投稿も時間がたてば流れていくだけ。

自分がタイムラインを覗いた「その時その場」がすべて。

投稿をするのも投稿を読むのも、知らなかった誰かを知るのも「その時その場」のたまたまのできごと。


個人が使うツールとして、それまでの流行りだったコンテンツ蓄積系のブログに対する解答、てところだろう…ツイッターはまさにその最適解だと思ったんだけど。


ひらたく例えると「汲み取り便所に対する水洗便所」みたいなもの。


これは個人の好みとか志向なのでどっちがどうとは言えないし、用途次第ということもあるだろうけど、twitterやfacebookは水洗便所として登場したんだから、ちゃんと綺麗に流してほしい。


ツイートやフィードをアーカイブする、まとめることができたり、検索できたりするのは「リアルタイムWEB」の理念から遠いんじゃないかな。

そういったことは蓄積系のブログや従来のWEBページの役割だろうに。


前にどこかで書いたけど。奇想天外だかのかんべむさしと堀晃の昭和の頃の対談でのネタ。


駅のトイレに入ってたら「自分以外の時間が逆流を始めて」流そうとしていた自分のクソが便器の中で鎌首をもたげて、肛門を狙ってとびかかってくる恐怖。


ツイッターで検索できたり、アーカイブできたりするのはこういうこと(違


イーロン閣下ツイッターのグダグダで、ツイッターの後、ツイッター以外が話題になってる。

facebook、instagramと同じMetaがサービス開始したthreadsの恣意的に操作されるタイムラインなど論外で、ただただ流れる情報をたまたま何かの縁で拾える「袖すり合うも他生の縁」みたいな、リアルタイムWEBなSNSがあれば面白いんだけどねえ。

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のコピペミスが原因。

サインする、検証するやりかたは上記エントリ通りで間違いなかった。


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

↑署名対象文字列、HTTPSignatureヘッダでも悶絶吐血の記録


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を使えば良かった

古代メキシコ展

2023/7/14 [00:20:28] (金) 天気

トーハクこと東京国立博物館で開催中の古代メキシコ展に行ってきた。

テオティワカン、アステカ、マヤ文明の出土品の展示で、目玉は赤の女王というミイラ。

image
image
image
image
image

相変わらずトーハクの展示は迫力だったなあ。見ていてまったく飽きないし、時間が足りない。


神々の犠牲があって成り立っている、という世界観?

なので生贄を捧げることになってたらしい。


にしても、神さまってのは人間につきものなんだなあ。


科学博物館でもペルー展(インカ)をやってるということで行ってみたけど、こちらは小さなコーナーで正直拍子抜け。

だけど、こっちは常設がスゲー。今さらながらびっくりした。

江戸時代あたりの日本は時間が一定じゃなく、季節ごと夜日中ごとで違ってたなんて、この歳になって初めて知った。

昼と夜、春夏秋冬で時間の流れかたが違うというのは、感覚的にはこっちが正しい時間のような気もする。

で、それをはかるための時計作りに心血を注いでいたという胸熱な展開もあったらしい。


今なんどきだい?という問いかけが実はかなり意味深だったんだなあ(え?


上野公園って、そこにある全部の展示と動物園まで見て回ると3泊でも足りないんじゃないかな。

で、ホテル代を払ってでも全部回る価値はありそう。

<<2026/1>>
    123
45678910
11121314151617
18192021222324
25262728293031
検索:

【最近の20件】