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?

HTTPメッセージ署名の検証

0
Last updated at Posted at 2025-12-16

はじめに

RFC 9421 HTTP Message Signatures 仕様の実装である Java ライブラリ『authlete/http-message-signatures』をオープンソースで公開しているのですが、先月、「署名検証用シグネチャベースの再構築手順を示すテストがない」という指摘を受けました (Issue 1)。

諸般の事情で詳細ドキュメントの執筆を先延ばししているため、今のところ実装コードやテストコードを読まないとライブラリの使い方は分かりません。そこで、RFC 9421 の Appendix に載っている HTTP メッセージ署名を検証するテストコード RFC9421Test.java をライブラリに追加しました。

本記事では、このテストコードに沿って HTTP メッセージ署名を検証する手順を見ていきます。

RFC 9421 の概要については Authlete 社のウェブサイトにある文書『標準仕様による徹底的な API 保護』の『HTTP メッセージ署名』をご参照ください。

HTTP メッセージ署名検証手順

Signature HTTP フィールドをパースする

署名された HTTP メッセージには Signature HTTP フィールドが含まれています。RFC 9421 の B.2.4. Signing a Response Using ecdsa-p256-sha256 には次の例が載せられています。

Signature HTTP フィールドの例 (RFC 9421, B.2.4 より抜粋)
Signature: sig-b24=:wNmSUAhwb5LxtOtOpNa6W5xj067m5hFrj0XQ4fvpaCLx0NK\
  ocgPquLgyahnzDnDAUy5eCdlYUEkLIj+32oiasw==:

Signature HTTP フィールドの値のフォーマットはディクショナリ (RFC 8941 Section 3.2) です。個々のキー・バリューの組は、任意のラベルと、バイトシーケンス (RFC 8491 Section 3.3.5) で表現された署名です。

Signature HTTP フィールドのフォーマット
Signature: ラベル=:Base64エンコードされた署名:, ラベル=:Base64エンコードされた署名:, ...

http-message-signatures ライブラリには Signature HTTP フィールドを表すユーティリティクラス SignatureField があるので、それを用いてパースします。

Signature HTTP フィールドをパースする
// Signature HTTP フィールドの値
String signatureFieldValue =
        "sig-b24=:wNmSUAhwb5LxtOtOpNa6W5xj067m5hFrj0XQ4fvpaCLx0NK" +
        "ocgPquLgyahnzDnDAUy5eCdlYUEkLIj+32oiasw==:";

// Signature HTTP フィールドの値をパースする
SignatureField signatureField =
        SignatureField.parse(signatureFieldValue);

Signature-Input HTTP フィールドをパースする

署名された HTTP メッセージには Signature-Input HTTP フィールドも含まれています。RFC 9421 の B.2.4. Signing a Response Using ecdsa-p256-sha256 には次の例が載せられています。

Signature-Input HTTP フィールドの例 (RFC 9421, B.2.4 より抜粋)
Signature-Input: sig-b24=("@status" "content-type" \
  "content-digest" "content-length");created=1618884473\
  ;keyid="test-key-ecc-p256"

Signature-Input HTTP フィールドの値のフォーマットもディクショナリ (RFC 8941 Section 3.2) です。個々のキー・バリューの組は、任意のラベルと、内部リスト (RFC 8941 Section 3.1.1) で表現されたメタデータです。

Signature-Input HTTP フィールドのフォーマット
Signature-Input: ラベル=(コンポーネント識別子群)任意パラメータ群, ラベル=(コンポーネント識別子群)任意パラメータ群, ...

http-message-signatures ライブラリには Signature-Input HTTP フィールドを表すユーティリティクラス SignatureInputField があるので、それを用いてパースします。

Signature-Input HTTP フィールドをパースする
// Signature-Input HTTP フィールドの値
String signatureInputFieldValue =
        "sig-b24=(\"@status\" \"content-type\" " +
        "\"content-digest\" \"content-length\");created=1618884473" +
        ";keyid=\"test-key-ecc-p256\"";

// Signature-Input HTTP フィールドの値をパースする
SignatureInputField signatureInputField =
        SignatureInputField.parse(signatureInputFieldValue);

署名・メタデータのペアを抽出する

署名とメタデータはそれぞれ、Signature HTTP フィールドと Signature-Input HTTP フィールドに分かれて置かれています。ペアとなっている署名とメタデータには同じラベルが付けられています。RFC 9421 B.2.4. の例では、ラベルとして sig-b24 が使われています。

Signature HTTP フィールドと Signature-Input HTTP フィールドの両方を調べ、署名・メタデータのペアを抽出処理は、SignatureEntry クラスの scan メソッドが担います。

署名・メタデータのペアを抽出する
Map<String, SignatureEntry> signatureEntries =
        SignatureEntry.scan(signatureField, signatureInputField);

scan メソッドが返す Map インスタンスのキーはラベルです。ラベルを指定することで、特定の SignatureEntry インスタンスを取り出すことができます。

特定の署名・メタデータを取り出す
SignatureEntry signatureEntry = signatureEntries.get("sig-b24");

SignatureEntry クラスには、ラベル (String)、署名 (byte[])、メタデータ (SignatureMetadata) を表すプロパティがあり、それぞれ、getLabel()getSignature()getMetadata() メソッドでアクセスできます。

コンポーネント値を用意する

RFC 9421 B.2. Test Cases のテスト群では、HTTP レスポンスを使うテストでは共通して下記の HTTP メッセージを用います。

テストで用いる HTTP レスポンス
HTTP/1.1 200 OK
Date: Tue, 20 Apr 2021 02:07:56 GMT
Content-Type: application/json
Content-Digest: sha-512=:mEWXIS7MaLRuGgxOBdODa3xqM1XdEvxoYhvlCFJ41Q\
  JgJc4GTsPp29l5oGX69wWdXymyU0rjJuahq4l5aGgfLQ==:
Content-Length: 23

{"message": "good dog"}

シグネチャベースを構築するのに先立ち、この HTTP メッセージに含まれる HTTP メッセージコンポーネント群 (RFC 9421, 2. HTTP Message Components) にアクセスするための SignatureContext を用意する必要があります。

ComponentValueProvider クラスを利用すると、比較的簡単に SignatureContext インターフェースの実装を用意できます。

SignatureContext インターフェースの実装
class ResponseSignatureContext extends ComponentValueProvider
{
    ResponseSignatureContext()
    {
        setStatus(200);
        setHeaders(buildHeaders());
    }

    private static Map<String, List<String>> buildHeaders()
    {
        Map<String, List<String>> headers = new LinkedHashMap<>();

        headers.put("Date",           List.of("Tue, 20 Apr 2021 02:07:56 GMT"));
        headers.put("Content-Type",   List.of("application/json"));
        headers.put("Content-Digest", List.of("sha-512=:mEWXIS7MaLRuGgxOBdODa3xqM1XdEvxoYhvlCFJ41QJgJc4GTsPp29l5oGX69wWdXymyU0rjJuahq4l5aGgfLQ==:"));
        headers.put("Content-Length", List.of("23"));

        return headers;
    }
}

シグネチャベースを構築する

シグネチャベースを構築するためのユーティリティクラス SignatureBaseBuilder に、SignatureContextSignatureMetadata を与えることで、シグネチャベースを表す SignatureBase インスタンスを生成できます。

シグネチャベースを構築する
SignatureBase signatureBase =
        new SignatureBaseBuilder(new ResponseSignatureContext())
        .build(signatureEntry.getMetadata());

signature_base_syntax.png

署名検証鍵を用意する

B.2.4 の HTTP メッセージ署名は B.1.3. Example ECC P-256 Test Key の鍵を用いて生成されています。この鍵を元に署名検証用の鍵を用意します。

署名検証鍵を用意する
// RFC 9421, B.1.3. Example ECC P-256 Test Key の鍵
// (注: ただし "alg" を追加している)
String TEST_KEY_ECC_P256 =
        "{\n" +
        "  \"kty\": \"EC\",\n" +
        "  \"alg\": \"ES256\",\n" +
        "  \"crv\": \"P-256\",\n" +
        "  \"kid\": \"test-key-ecc-p256\",\n" +
        "  \"d\": \"UpuF81l-kOxbjf7T4mNSv0r5tN67Gim7rnf6EFpcYDs\",\n" +
        "  \"x\": \"qIVYZVLCrPZHGHjP17CTW0_-D9Lfw0EkjqF7xB4FivA\",\n" +
        "  \"y\": \"Mc4nN9LTDOBhfoUeg8Ye9WedFRhnZXZJA12Qp0zZ6F0\"\n" +
        "}";

// 署名検証用の鍵を用意する
// (注: JWK は、Nimbus JOSE + JWT ライブラリのもの)
JWK verificationKey = JWK.parse(TEST_KEY_ECC_P256).toPublicJWK();

署名検証器を用意する

署名検証器は HttpVerifier インターフェースで表されます。JWK インスタンスが用意できていれば、このインターフェースの実装である JoseHttpVerifier クラスのインスタンスを生成できます。

署名検証器を用意する
HttpVerifier verifier = new JoseHttpVerifier(verificationKey);

署名を検証する

SignatureBase クラスの verify メソッドに署名検証器 (HttpVerifier) と署名 (byte[]) を渡し、署名を検証します。署名にパスすれば、戻り値は true になります。

署名を検証する
boolean verified = signatureBase.verify(verifier, signatureEntry.getSignature());

おわりに

HTTP メッセージ署名は API を保護する仕組みの一つとして利用できます。2025 年 10 月 29 日に開催したオンライン勉強会『OAuth・OpenID 標準仕様による徹底的な API 保護』でも紹介しておりますので、ご興味があれば録画をご視聴ください。

OAuth・OpenID 標準仕様による徹底的な API 保護 | HTTP メッセージ署名
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?