0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

当記事執筆時点では Web アプリケーションフレームワークによる Content-Digest HTTP フィールドの自動検証は一般的ではないので、検証コードを自分で書いていきます。

Content-Digest HTTP フィールド

RFC 9530 Digest FieldsSection 2. The Content-Digest Field で定義されている Content-Digest HTTP フィールドは、メッセージボディのダイジェスト値を含んでいます。このダイジェスト値と、実際のメッセージボディのダイジェスト値が一致しなければ、メッセージボディが改竄されたと判断することができます。

ただし、メッセージボディと Content-Digest HTTP フィールドの内容が同時に改竄された場合、ダイジェスト値の比較だけでは改竄を検出できません。Content-Digest HTTP フィールド改竄の可能性も考慮する場合は、HTTP メッセージ署名 (RFC 9421 HTTP Message Signatures) も併せて利用するなどの追加対策が必要です。

参考: FAPI 2.0 Http Signatures

Content-Digest HTTP フィールドの値のフォーマットは RFC 8941 Structured Field Values for HTTPSection 3.2. Dictionaries で定義されているディクショナリです。ディクショナリの形式は、キー・バリューの組をカンマ区切りで列挙したものです。

ディクショナリの形式 (RFC 8941 Section 3.2 より抜粋)
sf-dictionary  = dict-member *( OWS "," OWS dict-member )
dict-member    = member-key ( parameters / ( "=" member-value ))
member-key     = key
member-value   = sf-item / inner-list

Content-Digest HTTP フィールドの場合、ディクショナリのキーはハッシュアルゴリズムの名前、バリューはそのハッシュアルゴリズムを用いて計算したメッセージボディのダイジェスト値です。下記は RFC 9530 から抜粋した Content-Digest HTTP フィールドの例です。

Content-Digest の例 (RFC 9530 Section 2 より抜粋)
Content-Digest: \
  sha-256=:d435Qo+nKZ+gLcUHn7GQtQ72hiBVAgqoLsZnZPiTGPk=:,\
  sha-512=:YMAam51Jz/jOATT6/zvHrLVgOYTGFy1d6GJiOHTohq4yP+pgk4vf2aCs\
  yRZOtw8MjkM7iw7yZ/WkppmM44T3qg==:

この例にはキー・バリューの組が二つ含まれています。一つ目のキーは sha-256 で、二つ目のキーは sha-512 です。これらのキーはハッシュアルゴリズムを表しており、有効な値は IANA Hash Algorithms for HTTP Digest Fields に登録されています。

IANA Hash Algorithms for HTTP Digest Fields に登録されているアルゴリズムのうち、本記事執筆時点で Status=Active となっているのは sha-256sha-512 のみです。

ダイジェスト値は RFC 8941 Structured Field Values for HTTPSection 3.3.5. Byte Sequences で定義されているバイトシーケンス形式で表現されます。バイトシーケンスの形式は、データの Base64 表現の両端にコロンを置いたものです。

バイトシーケンスの形式 (RFC 8941 Section 3.3.5 より抜粋)
sf-binary = ":" *(base64) ":"
base64    = ALPHA / DIGIT / "+" / "/" / "="

Content-Digest の検証

Content-Digest の検証処理の簡易実装を下記に示します。処理の流れが分かりやすくなるよう、エラー処理や詳細情報の返却などは行なっていません。

ContentDigestVerification.java
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Map;
import org.greenbytes.http.sfv.ByteSequenceItem;
import org.greenbytes.http.sfv.Dictionary;
import org.greenbytes.http.sfv.ListElement;
import org.greenbytes.http.sfv.Parser;

public class ContentDigestVerification
{
    public static void main(String[] args) throws Exception
    {
        // Content-Digest HTTP フィールドの値 (RFC 9530 Appendix D より)
        String contentDigest = """
            sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+TaPm+\
            AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:,\
            sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:""";

        // メッセージボディの値 (RFC 9530 Appendix D より)
        byte[] content = "{\"hello\": \"world\"}".getBytes(StandardCharsets.UTF_8);

        // Content-Digest を検証する。
        boolean passed = verify(contentDigest, content);

        // 検証結果を出力する。
        System.out.println(passed);
    }

    private static boolean verify(String contentDigest, byte[] content) throws Exception
    {
        // Content-Digest HTTP フィールドの値をディクショナリとしてパースする。
        Dictionary dictionary = Parser.parseDictionary(contentDigest);

        // ディクショナリ内の各キー・バリューの組を一つ一つ処理していく。
        for (Map.Entry<String, ListElement<?>> member : dictionary.get().entrySet())
        {
            // ハッシュアルゴリズム (キー)
            String algorithm = member.getKey();

            // ダイジェスト値 (バリューをバイトシーケンスとして解釈してからバイト配列として取り出す)
            byte[] digest = ((ByteSequenceItem)member.getValue()).get().array();

            // ハッシュアルゴリズムを用いてメッセージボディのダイジェスト値を計算する。
            byte[] computedDigest = MessageDigest.getInstance(algorithm).digest(content);

            // ダイジェスト値が一致しなければ
            if (!MessageDigest.isEqual(digest, computedDigest))
            {
                // 検証をパスできなかった。
                return false;
            }
        }

        // 検証をパスした。
        return true;
    }
}

ソースコード内の org.greenbytes.http.sfv.* クラス群は structured-fields ライブラリのものです。

このプログラムを実行すると、

java -cp structured-fields.jar ContentDigestVerification.java

検証をパスしたことを示す true という文字列が出力されます。

true

おわりに

Content-Digest の仕様 (RFC 9530) と HTTP メッセージ署名の仕様 (RFC 9421) はお互いを参照しており、同時並行で仕様策定作業が進められたことを示唆しています。

FAPI 2.0 Http Signatures はこれらの仕様を土台とし、HTTP メッセージの完全性 (integrity) や真正性 (authenticity) を保証します。詳細については、2024 年のアドベントカレンダーの記事『FAPI 2.0 HTTP Signingの紹介』や、Authlete 社のウェブサイト上の記事『標準仕様による徹底的な API 保護』をご参照ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?