HTTP Signatureの解析

ActivityPubで必須のHTTP Signature解析のためのサブルーチン

たぶん、ActivityPubを自作実装する時の最初のハードルがHTTP Signatureの作成と検証だと思う。

署名の作成や検証にはperlの優秀なモジュールを利用させていただいている。
「Crypt::Perl - Cryptography in pure Perl」

以下は、上記perlのモジュールに渡す署名対象文字列を作るための作業メモ。

「Fediverseに参加するための最低条件2」
↑当サイトの別ページにも何をやってるか書いたけど、こっちでは具体的なperlのコードを。

perlの「%ENV」で取得する環境変数データ


CONTENT_LENGTH  1796
CONTENT_TYPE  application/activity+json
CONTEXT_DOCUMENT_ROOT  /home/users/1/lolipop.jp/web
CONTEXT_PREFIX  
DOCUMENT_ROOT  /home/users/1/lolipop.jp/web
GATEWAY_INTERFACE  CGI/1.1
HTTPS  on
HTTP_DATE  Fri, 09 Jan 2026 05:21:30 GMT
HTTP_DIGEST  SHA-256=PaoEWV/zUadTRlY35xURS08pc0cNvWdv/SqFEh10rNg=
HTTP_HOST  bookshelf.doncha.net
HTTP_SIGNATURE  keyId="https://tokoroten.doncha.net/t2aki#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="N9ICYILzDulDOtRUquopLvTkm4nd1lGsPgl8OTFvOkL7XlQWkSFGl8lYNUnX2ct3p9IVCP7xhgbGBoBi89Z3MIzIMMiVS1AiEqm6OqZlL6+rM/Sg/3ECkSePMM9rHvWKc38vZSESpRd3ZLDXoNFPcz1h1/RJuqk6018CSj8ZS+xDCZFfLHLwKlQZSqmqd+wPMiIe3o+G/bG2eIofcRMaQnW1+CQ74NSrDVcje/C16c6uPLWFY1J9lBoHQ9C+Mkbk6Uy5eoGslZEOvFHIAAdf9PN1d1R9mZVREaetqGCfdJ4ee9hAlLUkAHTdXRZmdHKzvjNgRBlqJWjkYZWassXXwA=="
HTTP_USER_AGENT  wwwlibperl (tokoroten; +https://tokoroten.doncha.net/)
HTTP_X_BACKEND  lolipop.lan
HTTP_X_FORWARDED_FOR  NNN.NNN.NNN.NNN
HTTP_X_FORWARDED_HOST  bookshelf.doncha.net
HTTP_X_FORWARDED_PROTO  https
NSS_SDB_USE_CACHE  YES
PATH  /usr/local/bin:/usr/bin:/bin
QUERY_STRING  
REDIRECT_STATUS  200
REDIRECT_URL  /librarian/inbox
REMOTE_ADDR  NNN.NNN.NNN.NNN
REMOTE_PORT  56200
REQUEST_METHOD  POST
REQUEST_SCHEME  https
REQUEST_URI  /librarian/inbox
SCRIPT_FILENAME  /home/users/1/lolipop.jp/web/script.cgi
SCRIPT_NAME  /script.cgi
SERVER_ADDR  NNN.NNN.NNN.NNN
SERVER_ADMIN  https://lolipop.jp/support/
SERVER_NAME  bookshelf.doncha.net
SERVER_PORT  443
SERVER_PROTOCOL  HTTP/1.1
SERVER_SIGNATURE  
SERVER_SOFTWARE  Apache
SSL_TLS_SNI  bookshelf.doncha.net
UNIQUE_ID  aWCQWn3aBn64anct-K1sQQAAAVU
	

ex) 「HTTP_DATE」がキー、「Fri, 09 Jan 2026 05:21:30 GMT」がその値

Signature(署名)は「HTTP_SIGNATURE」にある。
検証に必要なのは「date」だけど、「HTTP_DATE」とアルファベットが大文字で「HTTP_」というプレフィックスもついてる。

【手順その1】

  1. アルファベットをすべて小文字に置換
  2. プレフィックス「http_」「request_」を削除
  3. 「_」を「-」に置換
  4. アルファベット小文字をキーにしたhash(%$h)に値を入れておく

【手順その2】

  1. その1で用意したhashのうち「signature」を「,」でバラす
  2. バラした要素を「=」でキーと値にバラす
  3. 値についてる「"」は不要なので削除
  4. hash(%$sig)に入れておく

【手順その3】

headersから署名対象となる文字列を作る。
たぶん、ここが一番わかりにくいところ(わたしは)

「headers」に記載されている順番通りに文字列として組み立てる

ひとつずつ「キー[: ](コロンと半角空白)値」の文字列にして、最後に全部を「改行」で繋いだ文字列にしたものが、署名の対象文字列となる。

  1. その2で用意した「signature」のhashの「headers」を「 」(半角空白)でバラす
    ex)「(request-target) host date digest content-type」
    この例では対象になるのは5つだけど、サーバーによって違うので「headers」の確認は必須。
    ※ここにある要素名はそのままその1で用意したhashのキーと同じになる。
  2. 「(request-target)」だけ別扱い
    その1で用意したhashの「method」と「uri」の値を半角空白で繋いで値に入れて文字列作成。
  3. それ以外は、その1とその2で用意したhashでキーは同じ(headersのキー=%$hのキー)なのでそのまま%$hの値を入れて文字列作成。
    文字列→「キー[: ](コロンと半角空白)値」
  4. push()を使って順番通りに配列に入れていく。
  5. 最後に配列の要素を「改行」で繋いだ文字列を作ったら完成
    ↓文字列
    「キー[: ](コロンと半角空白)値[改行]」
    「キー[: ](コロンと半角空白)値[改行]」
    (「Fediverseに参加するための最低条件2」冒頭に記載した別ページに完成形の具体例があります)

sub parse_request{
    my $self = shift;
    my $args = shift;

    my $h;
    foreach (keys %ENV){
        my $k = $_;
        $k =~ tr/A-Z/a-z/; $k =~ s!http_!!; $k =~ s!request_!!; $k =~ s!_!-!g;
        $h->{$k} = $ENV{$_};
    }
    my $sig;
    foreach ( split(',', $h->{signature}) ){
        my ( $k,$v ) = split('=', $_);
        $v =~ s!\"!!g;
        $sig->{$k} = $v;
    }

    my @sign_items;
    foreach ( split(' ', $sig->{headers} ) ){
        if( $_ eq '(request-target)'){
            $h->{method} =~ tr/A-Z/a-z/;
            push(@sign_items, sprintf( qq{%s: %s %s},$_ , $h->{method}, $h->{uri} ) );
        }
        else{
            push(@sign_items, sprintf(qq{%s: %s}, $_, $h->{$_}));
        }
    }
    my $sign_key = join("\n", @sign_items);
    return {signature=>$sig->{signature}, sign_key=>$sign_key, key_id=>$sig->{keyId} };
}
	

署名対象文字列を作ってしまえば、後はありがたく使わせていただいているperlのモジュールに渡すだけ。

署名にあった「keyId」に、アカウント情報をリクエストして「公開鍵」を取得。
公開鍵を使って署名と対象文字列の検証をしてもらう。

このサブルーチンは呪文状態で使っていてコピペだけで、こと足りる。


sub verify_signature{
    my $self = shift;
    my $args = shift;

    my $content = $self->get_actpb({url=>$args->{key_id}});
    return if( ! $content );

    my $json;
    eval{
        $json = decode_json($content);
    };
    if( $@ ){
        printf qq{ERROR veri JSON %s}, $@;
        printf qq{ url : %s}, $args->{key_id};
        return;
	}
    my $pub = $json->{publicKey}->{publicKeyPem};
    my $publickey  = Crypt::Perl::RSA::Parse::public($pub);
    my $decoded = decode_base64($args->{signature});
    return $publickey->verify_RS256($args->{sign_key}, $decoded);
}
	

このへん、しょっちゅう忘れるのでメモ

[2026-01-11 07:34:42] v1.0.1

[2026-01-10 10:18:41] v1.0.0

Menu