自由帳

@_nibral の技術ブログ

RustでActivityPubサーバを書いていてハマったところ

ここ半年くらいMisskeyの某サーバに定住していて、裏で動いている仕組みが気になったので勉強がてらRustでActivityPubのサーバを書いた。といってもガチのアプリケーションではなく、複数のBOTユーザを抱えてフォロワーにノートを配信するだけの片方向な感じのやつ。

実装を進めていくと何回かどうやったらいいのかわからなくてハマったので備忘録として残す。

github.com

HTTP SignatureのDigestの求め方

sha256base64を使う。SHA-256のハッシュ値を2文字ずつに分割して16進数として解釈し、BASE64エンコードすればOK。なんかもうちょっとうまいやり方があるような気もする。

fn http_digest(data: String) -> String {
    let sha256_hash = digest(data);
    let binaries = sha256_hash.chars()
        .collect::<Vec<char>>()
        .chunks(2)
        .map(|c| c.iter().collect::<String>())
        .map(|hex| u8::from_str_radix(&hex, 16).unwrap())
        .collect::<Vec<u8>>();
    return general_purpose::STANDARD.encode(binaries);
}

HTTPリクエストの署名

Signatureヘッダに含める署名は以下で求めることができる。実際にはユーザごとの秘密鍵をDBに保存しておいて、都度必要な鍵を取り出して署名することになると思う。

fn http_sign(data: &[u8]) -> String {
    // load pem
    let private_key_str = r#"-----BEGIN RSA PRIVATE KEY-----
........
-----END RSA PRIVATE KEY-----"#;
    let private_key = PKey::private_key_from_pem(private_key_str.as_bytes()).unwrap();

    // signing by rsa-sha256
    let mut signer = Signer::new(MessageDigest::sha256(), &private_key).unwrap();
    signer.set_rsa_padding(Padding::PKCS1).unwrap();
    let mut len = signer.len().unwrap();
    let mut buf = vec![0; len];
    len = signer.sign_oneshot(&mut buf, data).unwrap();
    buf.truncate(len);
    let signature = general_purpose::STANDARD.encode(buf);

    return signature;
}

Actix Webに非同期でデータを渡す

Dependency InjectionとかでActix Webのサーバを起動するときにデータを渡しておき、リクエストハンドラの引数として受け取りたいケース。公式をはじめとして見つけやすいサンプルコードでは App:new().app_data() を使っているが、この方法はasync/awaitに対応していない。代わりに App::new().data_factory() を使う。

リクエストハンドラ側の受け取り方は一緒。

 App::new()
    .data_factory(|| {
        async {
            <ここにawaitな処理を書く>
        }
    })