RFC 8785 JSON Canonicalization Scheme June 2020
Rundgren, et al. Informational [Page]
Stream:
Independent Submission
RFC:
8785
Category:
Informational
Published:
ISSN:
2070-1721
Authors:
A. Rundgren
Independent
B. Jordan
Broadcom
S. Erdtman
Spotify AB

RFC 8785

JSON Canonicalization Scheme (JCS)

概要

ハッシュ化や署名のような暗号操作では、操作を確実に 繰り返せるように、データが不変の形式で表現されている 必要がある。 これに対処する 1 つの方法は、データの正準表現を作成する ことである。正準化により、データを "wire" 上では元の形式で 交換しながら、生成側および消費側エンドポイントでデータの 正準化された対応物に対して行われる暗号操作が一貫した結果を 生成することも可能になる。

本文書は JSON Canonicalization Scheme (JCS) について記述する。 本仕様は、ECMAScript によって定義される JSON プリミティブの 厳密なシリアル化方法を基盤とし、JSON データを Internet JSON (I-JSON) サブセットに制約し、決定的なプロパティソートを 使用することで、JSON データの正準表現を作成する方法を定義する。

このメモの位置づけ

本文書は Internet Standards Track 仕様ではなく、情報提供を目的として 公開される。

これは、他の RFC ストリームとは独立した RFC Series への寄稿である。 RFC Editor はその裁量により本文書を公開することを選択しており、 実装または展開に対する価値についていかなる声明も行わない。 RFC Editor によって公開が承認された文書は、どの水準の Internet Standard の候補でもない。RFC 7841 の Section 2 を参照。

本文書の現在の状態、正誤表、およびフィードバックの送付方法に関する 情報は、 https://www.rfc-editor.org/info/rfc8785 で入手できる。

目次

1. 導入

本文書は JSON Canonicalization Scheme (JCS) について記述する。 本仕様は、ECMAScript [ECMA-262] によって 定義される JSON プリミティブの厳密なシリアル化方法を基盤とし、 JSON データを I-JSON [RFC7493] サブセットに制約し、決定的なプロパティソートを使用することで、 JSON [RFC8259] データの 正準表現を作成する方法を定義する。JCS の出力は、 暗号手法で使用できる JSON データの「ハッシュ可能な」表現である。 以降の段落では、主要な設計上の考慮事項の概要を述べる。

ハッシュ化や署名のような暗号操作では、操作を確実に 繰り返せるように、データが不変の形式で表現されている 必要がある。 これを実現する 1 つの方法は、base64url [RFC4648] のような、単純で固定された表現を持つ形式にデータを変換することである。 JSON Web Signature (JWS) [RFC7515] は、 この問題にそのように対処した。 もう 1 つの解決策は、XML signature [XMLDSIG] 標準で行われたことに似た、データの 正準バージョンを作成することである。

正準化スキームの主な利点は、データを元の形式のまま 保持できることである。これが JCS の中核的な根拠である。 別の言い方をすれば、正準化を使用することで、署名後であっても JSON オブジェクトを JSON オブジェクトのままにしておくことができる。 これは、システム設計、文書化、およびロギングを簡素化できる。

「車輪の再発明」を避けるため、JCS は、バージョン 6 以降の ECMAScript(別名 JavaScript) [ECMA-262] によって定義される JSON プリミティブ(文字列、数値、およびリテラル)のシリアル化に依存する。

経験豊富な XML 開発者は、XML 署名の検証を成功させる際の困難を 思い出すかもしれない。これは通常、非常に入り組んだ XML 正準化規則の 解釈の違い、および同じく複雑な Web Services セキュリティ標準の 解釈の違いによるものであった。JCS が同様の問題を抱えるべきではない 理由は次のとおりである。

JCS は、JSON Web Key (JWK) Thumbprint [RFC7638] や Keybase [KEYBASE] など、JSON 正準化に依存する 既存のいくつかのシステムと互換性がある。

暗号以外での潜在的な用途については、[JSONCOMP] を参照。

本文書の想定読者は、JSON ツールベンダー、および JSON ベースの 暗号ソリューションの設計者である。読者は、"JSON" オブジェクトを含む ECMAScript について知識を持っているものと想定される。

2. 用語

本文書は IETF standards track 上の文書ではないことに注意。 しかし、適合実装は、セキュリティおよび相互運用性の理由から、 指定された動作に従うものとされる。このテキストでは、 その必要な動作を記述するために BCP 14 を使用する。

本文書におけるキーワード "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", および "OPTIONAL" は、 ここに示されるようにすべて大文字で現れる場合に限り、 BCP 14 [RFC2119] [RFC8174] に記述されているとおりに 解釈されるものとする。

3. 詳細な動作

このセクションでは、正準 JSON 表現の作成に関連する詳細と、 それらが JCS によってどのように対処されるかについて記述する。

Appendix F は、既存の JSON ツールに JCS サポートを追加するための RECOMMENDED な方法を記述する。

3.1. 入力データの作成

正準的にシリアル化されるデータは、通常、次によって作成される。

  • 以前に生成された JSON データを解析する。
  • プログラムによってデータを作成する。

使用される方法に関係なく、シリアル化されるデータは I-JSON [RFC7493] 形式に適合させなければならず(MUST)、 これは次を意味する。

  • JSON オブジェクトは、重複するプロパティ名を示してはならない (MUST NOT)。
  • JSON 文字列データは Unicode [UNICODE] として表現可能でなければならない(MUST)。
  • JSON 数値データは IEEE 754 [IEEE754] 二倍精度値として表現可能でなければならない(MUST)。 IEEE 754 二倍精度が提供するよりも高い精度または長い整数を 必要とするアプリケーションでは、そのような数値を JSON 文字列として 表すことが RECOMMENDED である。 これを相互運用可能かつ拡張可能な方法で行う方法の詳細については、 Appendix D を参照。

追加の制約として、解析された JSON 文字列データは、その後のシリアル化中に 変更されてはならない(MUST NOT)。 詳細については、Appendix E を参照。

注: Unicode 標準は、「Unicode Normalization」[UCNORM] と呼ばれる特定の文字列を並べ替える可能性を提供しているが、 JCS 適合の文字列処理はこれを考慮しない。すなわち、 JCS に依存するスキームに関与するすべてのコンポーネントは、 Unicode 文字列データを「そのまま」保持しなければならない (MUST)。

3.2. 正準 JSON データの生成

次のサブセクションでは、前のセクションで詳述したデータの 正準 JSON 表現を作成するために必要な手順について記述する。

Appendix A は、 JCS 仕様に一致する、ECMAScript ベースの正準化器の サンプルコードを示す。

3.2.1. 空白

JSON トークン間の空白は出力してはならない(MUST NOT)。

3.2.2. プリミティブ データ型のシリアル化

次の JSON オブジェクトが解析されたと仮定する。

  {
    "numbers": [333333333.33333329, 1E30, 4.50,
                2e-3, 0.000000000000000000000000001],
    "string": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/",
    "literals": [null, true, false]
  }

解析されたデータを、その後 ECMAScript の "JSON.stringify()" に 適合するシリアライザーを使用してシリアル化した場合、 結果は(表示目的のみで改行を追加すると)元のデータとはかなり 異なるものになる。

  {"numbers":[333333333.3333333,1e+30,4.5,0.002,1e-27],"string":
  "€$\u000f\nA'B\"\\\\\"/","literals":[null,true,false]}

解析されたデータと、そのシリアル化された対応物との違いの理由は、 入力データには(JSON [RFC8259] によって定義されるように)広い許容範囲がある一方で、 出力データには(ECMAScript によって定義されるように) 固定された表現があるためである。例に見られるように、 数値も丸めの対象となる。

次のサブセクションでは、JCS に従ったプリミティブ JSON データ型のシリアル化について記述する。この部分は ECMAScript のものと 同一である。将来の ECMAScript バージョンが、次のいずれかの シリアル化方法を無効にするという(起こりそうにない)事態では、 この仕様に従い続けるか、新しい仕様を作成するかは開発者コミュニティに 委ねられる。

3.2.2.1. リテラルのシリアル化

JSON [RFC8259] に従い、 リテラル "null"、"true"、および "false" は、それぞれ null、true、および false として シリアル化されなければならない(MUST)。

3.2.2.2. 文字列のシリアル化

JSON 文字列データ(JSON オブジェクトのプロパティ名も含む)について、 各 Unicode コードポイントは、以下に記述されるように シリアル化されなければならない(MUST) ([ECMA-262] の Section 24.3.2.2 を参照)。

  • Unicode 値が従来の ASCII 制御文字範囲 (U+0000 から U+001F)内にある場合、 それが事前定義済みの JSON 制御文字 U+0008、U+0009、U+000A、 U+000C、または U+000D の集合に含まれない限り、 小文字 16 進 Unicode 表記(\uhhhh)を使用して シリアル化されなければならない(MUST)。 これらの事前定義済み文字は、それぞれ \b、\t、\n、\f、および \r として シリアル化されなければならない(MUST)。
  • Unicode 値が ASCII 制御文字範囲の外にある場合、 それは U+005C (\) または U+0022 (") と同等でない限り、 "as is" でシリアル化されなければならない(MUST)。 それらは、それぞれ \\ および \" として シリアル化されなければならない(MUST)。

最後に、結果として得られる Unicode コードポイント列は、 二重引用符 (") で囲まれなければならない(MUST)。

注: "lone surrogates"(例: U+DEAD)のような無効な Unicode データは、 署名の破損を含む相互運用性の問題につながる可能性があるため、 そのようなデータの出現は、適合する JCS 実装を適切なエラーで 終了させなければならない(MUST)。

3.2.2.3. 数値のシリアル化

ECMAScript は、JSON 数値データを表すために IEEE 754 [IEEE754] 二倍精度標準を基盤とする。 そのようなデータは、"Note 2" 拡張を含め、 [ECMA-262] の Section 7.1.12.1 に従って シリアル化されなければならない(MUST)。

この部分は比較的複雑であるため、アルゴリズム自体は 本文書には含めない。JCS 適合の数値シリアル化の実装者にとって、 Google の V8 [V8] における実装は 参照として役立つ可能性がある。 もう 1 つの互換性のある数値シリアル化の参照実装は Ryu [RYU] であり、 これは Appendix G で言及されている JCS オープンソース Java 実装で使用されている。 Appendix B には、 IEEE 754 サンプル値と、それに対応する JSON シリアル化の集合が 収められている。

注: Not a Number (NaN) および Infinity は JSON で許可されていないため、 NaN または Infinity の出現は、適合する JCS 実装を適切なエラーで 終了させなければならない(MUST)。

3.2.3. オブジェクト プロパティのソート

前の手順でプリミティブ JSON データ型の表現は正規化されたが、 結果はまだ "canonical" とはみなされない。これは JSON オブジェクトの プロパティが辞書式(アルファベット順)になっていないためである。

Section 3.2.2 のサンプルに適用すると、 適切に正準化されたバージョンは(表示目的のみで改行を追加すると) 次のようになるべきである。

  {"literals":[null,true,false],"numbers":[333333333.3333333,
  1e+30,4.5,0.002,1e-27],"string":"€$\u000f\nA'B\"\\\\\"/"}

JCS に従う JSON オブジェクトプロパティの辞書式ソート規則は 次のとおりである。

  • JSON オブジェクトプロパティは再帰的にソートされなければならない (MUST)。これは、JSON 子オブジェクトも そのプロパティをソートされなければならない(MUST) ことを意味する。
  • JSON 配列データも JSON オブジェクトの存在を調べるために 走査されなければならない(MUST) (オブジェクトが見つかった場合、そのプロパティはソートされなければならない (MUST))が、配列要素の順序は 変更してはならない(MUST NOT)。

JSON オブジェクトのプロパティをソートしようとするとき、 次の措置に従わなければならない(MUST)。

  • ソート処理は、プロパティ名文字列の "raw"(エスケープされていない) 形式に適用される。つまり、改行文字は U+000A として扱われる。
  • ソートされるプロパティ名文字列は、 UTF-16 [UNICODE] コード単位の配列として整形される。 ソートは純粋な値比較に基づき、コード単位はロケール設定とは 独立した符号なし整数として扱われる。
  • プロパティ名文字列は、両方の文字列で有効なインデックスである ある位置に異なる値を持つか、長さが異なるか、またはその両方である。 1 つ以上のインデックス位置で異なる値を持つ場合、 k をそのような最小のインデックスとする。このとき、 k の位置にある値が、"<" 演算子を使用して決定される より小さい値である文字列が、辞書式に他方の文字列に先行する。 異なるインデックス位置が存在しない場合、短い文字列が 長い文字列に辞書式に先行する。

    平易に言えば、これはプロパティ名が次のような昇順で ソートされることを意味する。

            ""
            "a"
            "aa"
            "ab"
    

ソートアルゴリズムを UTF-16 コード単位に基づかせる理由は、 それが ECMAScript(Web ブラウザーおよび Node.js に搭載)、 Java、および .NET の文字列型に直接対応するためである。 さらに、JSON は UTF-16 コード単位として表現されるエスケープシーケンスのみを サポートするため、そのようなデータの知識と扱いはいずれにせよ 必要である。別の内部表現の文字列データを使用するシステムでは、 ソート前に JSON プロパティ名文字列を UTF-16 コード単位の配列へ 変換する必要がある。UTF-8 または UTF-32 から UTF-16 への変換は、 Unicode [UNICODE] 標準によって 定義される。

次の JSON テストデータは、JCS 実装におけるソートスキームの 正しさを検証するために使用できる。

  {
    "\u20ac": "Euro Sign",
    "\r": "Carriage Return",
    "\ufb33": "Hebrew Letter Dalet With Dagesh",
    "1": "One",
    "\ud83d\ude00": "Emoji: Grinning Face",
    "\u0080": "Control",
    "\u00f6": "Latin Small Letter O With Diaeresis"
  }

プロパティ文字列のソート後に期待される引数順序。

  "Carriage Return"
  "One"
  "Control"
  "Latin Small Letter O With Diaeresis"
  "Euro Sign"
  "Emoji: Grinning Face"
  "Hebrew Letter Dalet With Dagesh"

注: 決定的なプロパティ順序を得る目的では、 UTF-8 または UTF-32 で符号化されたデータをソートしても機能するが、 上記のような JSON データの結果は異なるため、本仕様とは 互換性がない。 しかし実際には、プロパティ名が 7 ビット ASCII の外側で定義されることは まれであるため、UTF-16 へ変換せずに UTF-8 または UTF-32 形式で 文字列データをソートしても、JCS と互換性を保つことが可能である。 これが実行可能な選択肢であるかどうかは、JCS が使用される 環境に依存する。

3.2.4. UTF-8 生成

最後に、プラットフォーム非依存の表現を作成するため、 前の手順の結果は UTF-8 で符号化されなければならない (MUST)。

Section 3.2.3 のサンプルに適用すると、 これは次のバイト列を生成するはずであり、ここでは 16 進表記で示す。

  7b 22 6c 69 74 65 72 61 6c 73 22 3a 5b 6e 75 6c 6c 2c 74 72
  75 65 2c 66 61 6c 73 65 5d 2c 22 6e 75 6d 62 65 72 73 22 3a
  5b 33 33 33 33 33 33 33 33 33 2e 33 33 33 33 33 33 33 2c 31
  65 2b 33 30 2c 34 2e 35 2c 30 2e 30 30 32 2c 31 65 2d 32 37
  5d 2c 22 73 74 72 69 6e 67 22 3a 22 e2 82 ac 24 5c 75 30 30
  30 66 5c 6e 41 27 42 5c 22 5c 5c 5c 5c 5c 22 2f 22 7d

このデータは、暗号手法への入力として使用できることを意図している。

4. IANA に関する考慮事項

本文書には IANA による措置はない。

5. セキュリティに関する考慮事項

バッファのオーバーフローや、システムの完全性に影響し得る同様の事柄を 避けるため、入力データに対して健全性チェックを行うことが重要である。

JCS が Appendix F に記述されるような 署名スキームに適用される場合、アプリケーションは受信データに基づいて 動作する前に、次の操作を実行しなければならない(MUST)。

  1. JSON データを解析し、それが I-JSON に従っていることを検証する。
  2. それが使用されるエコシステムによって定義された規約に従い、 データの正しさを検証する。これには、署名データを保持する プロパティを特定することも含まれる。
  3. 署名を検証する。

これらの手順のいずれかが失敗した場合、進行中の操作は 中止されなければならない(MUST)。

6. 参考文献

6.1. 規範的参考文献

[ECMA-262]
ECMA International, "ECMAScript 2019 Language Specification", Standard ECMA-262 10th Edition, , <https://www.ecma-international.org/ecma-262/10.0/index.html>.
[IEEE754]
IEEE, "IEEE Standard for Floating-Point Arithmetic", IEEE 754-2019, DOI 10.1109/IEEESTD.2019.8766229, <https://ieeexplore.ieee.org/document/8766229>.
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC7493]
Bray, T., Ed., "The I-JSON Message Format", RFC 7493, DOI 10.17487/RFC7493, , <https://www.rfc-editor.org/info/rfc7493>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/info/rfc8174>.
[RFC8259]
Bray, T., Ed., "The JavaScript Object Notation (JSON) Data Interchange Format", STD 90, RFC 8259, DOI 10.17487/RFC8259, , <https://www.rfc-editor.org/info/rfc8259>.
[UCNORM]
The Unicode Consortium, "Unicode Normalization Forms", <https://www.unicode.org/reports/tr15/>.
[UNICODE]
The Unicode Consortium, "The Unicode Standard", <https://www.unicode.org/versions/latest/>.

6.2. 参考情報としての参考文献

[JSONCOMP]
Rundgren, A., ""Comparable" JSON (JSONCOMP)", Work in Progress, Internet-Draft, draft-rundgren-comparable-json-04, , <https://tools.ietf.org/html/draft-rundgren-comparable-json-04>.
[KEYBASE]
Keybase, "Canonical Packings for JSON and Msgpack", <https://keybase.io/docs/api/1.0/canonical_packings>.
[NODEJS]
OpenJS Foundation, "Node.js", <https://nodejs.org>.
[OPENAPI]
OpenAPI Initiative, "The OpenAPI Specification: a broadly adopted industry standard for describing modern APIs", <https://www.openapis.org/>.
[RFC4648]
Josefsson, S., "The Base16, Base32, and Base64 Data Encodings", RFC 4648, DOI 10.17487/RFC4648, , <https://www.rfc-editor.org/info/rfc4648>.
[RFC7515]
Jones, M., Bradley, J., and N. Sakimura, "JSON Web Signature (JWS)", RFC 7515, DOI 10.17487/RFC7515, , <https://www.rfc-editor.org/info/rfc7515>.
[RFC7638]
Jones, M. and N. Sakimura, "JSON Web Key (JWK) Thumbprint", RFC 7638, DOI 10.17487/RFC7638, , <https://www.rfc-editor.org/info/rfc7638>.
[RYU]
"Ryu floating point number serializing algorithm", commit 27d3c55, , <https://github.com/ulfjack/ryu>.
[V8]
Google LLC, "What is V8?", <https://v8.dev/>.
[XMLDSIG]
W3C, "XML Signature Syntax and Processing Version 1.1", W3C Recommendation, , <https://www.w3.org/TR/xmldsig-core1/>.

Appendix A. ECMAScript サンプル 正準化器

以下は、ECMAScript ベースのシステムで使用するための JCS 正準化器の例である。

  ////////////////////////////////////////////////////////////
  // Since the primary purpose of this code is highlighting //
  // the core of the JCS algorithm, error handling and      //
  // UTF-8 generation were not implemented.                 //
  ////////////////////////////////////////////////////////////
  var canonicalize = function(object) {

      var buffer = '';
      serialize(object);
      return buffer;

      function serialize(object) {
          if (object === null || typeof object !== 'object' ||
              object.toJSON != null) {
              /////////////////////////////////////////////////
              // Primitive type or toJSON, use "JSON"        //
              /////////////////////////////////////////////////
              buffer += JSON.stringify(object);

          } else if (Array.isArray(object)) {
              /////////////////////////////////////////////////
              // Array - Maintain element order              //
              /////////////////////////////////////////////////
              buffer += '[';
              let next = false;
              object.forEach((element) => {
                  if (next) {
                      buffer += ',';
                  }
                  next = true;
                  /////////////////////////////////////////
                  // Array element - Recursive expansion //
                  /////////////////////////////////////////
                  serialize(element);
              });
              buffer += ']';

          } else {
              /////////////////////////////////////////////////
              // Object - Sort properties before serializing //
              /////////////////////////////////////////////////
              buffer += '{';
              let next = false;
              Object.keys(object).sort().forEach((property) => {
                  if (next) {
                      buffer += ',';
                  }
                  next = true;
                  /////////////////////////////////////////////
                  // Property names are strings, use "JSON"  //
                  /////////////////////////////////////////////
                  buffer += JSON.stringify(property);
                  buffer += ':';
                  //////////////////////////////////////////
                  // Property value - Recursive expansion //
                  //////////////////////////////////////////
                  serialize(object[property]);
              });
              buffer += '}';
          }
      }
  };

Appendix B. 数値シリアル化 サンプル

次の表は、いくつかの境界ケースを含む、 ECMAScript 互換の数値シリアル化サンプルの集合を収めている。 "IEEE 754" 列は、"Number" データ型の内部 ECMAScript 表現を指し、 これは IEEE 754 [IEEE754] 標準に基づく 64 ビット(二倍精度)値であり、ここでは 16 進数で表されている。

Table 1: ECMAScript 互換 JSON 数値 シリアル化サンプル
IEEE 754 JSON 表現 コメント
0000000000000000 0 ゼロ
8000000000000000 0 負のゼロ
0000000000000001 5e-324 最小の正の数
8000000000000001 -5e-324 最小の負の数
7fefffffffffffff 1.7976931348623157e+308 最大の正の数
ffefffffffffffff -1.7976931348623157e+308 最大の負の数
4340000000000000 9007199254740992 最大正整数 int (1)
c340000000000000 -9007199254740992 最大負整数 int (1)
4430000000000000 295147905179352830000 ~2**68 (2)
7fffffffffffffff NaN (3)
7ff0000000000000 Infinity (3)
44b52d02c7e14af5 9.999999999999997e+22
44b52d02c7e14af6 1e+23
44b52d02c7e14af7 1.0000000000000001e+23
444b1ae4d6e2ef4e 999999999999999700000
444b1ae4d6e2ef4f 999999999999999900000
444b1ae4d6e2ef50 1e+21
3eb0c6f7a0b5ed8c 9.999999999999997e-7
3eb0c6f7a0b5ed8d 0.000001
41b3de4355555553 333333333.3333332
41b3de4355555554 333333333.33333325
41b3de4355555555 333333333.3333333
41b3de4355555556 333333333.3333334
41b3de4355555557 333333333.33333343
becbf647612f3696 -0.0000033333333333333333
43143ff3c1cb0959 1424953923781206.2 偶数への丸め (4)

注:

(1)
ECMAScript の "JSON" オブジェクトとの最大限の適合性のため、 真の整数として解釈される値は、-9007199254740991 から 9007199254740991 の範囲内にあるべきである(SHOULD)。 ただし、アプリケーションで数値がどのように使用されるかは JCS アルゴリズムに影響しない。
(2)
2**68 のような特定の整数の集合は拡張精度を持つものと みなせる場合があるが、JCS/ECMAScript 数値シリアル化アルゴリズムは これを考慮しない。
(3)
範囲外の値は JSON では許可されない。 Section 3.2.2.3 を参照。
(4)
この数値は正確には 1424953923781206.25 であるが、 Section 3.2.2.3 で言及されている "Note 2" 規則の後、切り詰められ、最も近い偶数値に丸められる。

JCS 数値シリアライザーをより徹底的に検証するには、 開発ポータル(Appendix I を参照)で (現在)利用可能な、多数のサンプル値を含むファイルに対して テストできる。別の選択肢は、V8 [V8] をライブ参照として使用し、 大量のランダムな IEEE 754 値を生成するプログラムと一緒に実行することである。

Appendix C. "Wire Format" としての正準化 JSON

正準化処理(Section 3.2.4 を参照)の結果は完全に有効な JSON であるため、 "Wire Format" としても使用できる。ただし、これは単なる選択肢である。 JCS に基づく暗号スキームは、多くの場合、外部から供給される JSON データがすでに正準化されていることに依存しないためである。

実際、"JSON.stringify()" を使用してオブジェクトをシリアル化する ECMAScript 標準の方法は、プロパティが作成または受信された順序で 保持される、より「論理的な」形式を生成する。以下の例は、 ECMAScript 標準シリアル化の恩恵を受け得る住所レコードを示している。

  {
    "name": "John Doe",
    "address": "2000 Sunset Boulevard",
    "city": "Los Angeles",
    "zip": "90001",
    "state": "CA"
  }

正準化を使用すると、上記のプロパティは "address"、"city"、"name"、 "state"、および "zip" の順で出力され、人間(開発者または技術サポート)の 観点からデータに曖昧さを加える。正準化は JSON データを 1 行のテキストにも 変換するため、デバッグやロギングにはあまり理想的ではない場合がある。

Appendix D. 大きな数値の扱い

JSON 数値型にはいくつかの問題があり、ここでは次のサンプルオブジェクトで 例示する。

  {
    "giantNumber": 1.4e+9999,
    "payMeThis": 26000.33,
    "int64Max": 9223372036854775807
  }

上記のサンプルは JSON [RFC8259] に 適合しているが、アプリケーションは通常、 "giantNumber" と "int64Max" を格納するために異なるネイティブデータ型を使用する。 さらに、"payMeThis" のような金銭データは、十進演算に関する丸め問題のため、 おそらく浮動小数点データ型には依存しない。

この種の JSON 数値型の「多重利用」を扱う確立された方法 (少なくとも拡張可能な方法)は、マッピング機構を通じて、 名前に基づいて異なるプロパティをどう扱うかをパーサーに指示することである。 しかし、これは本来の、いくらか制約された JavaScript 文脈の外で JSON 数値型を使用する価値を大きく制限する。 ECMAScript の "JSON" オブジェクトも、JSON 数値型へのマッピングをサポートしない。

上記の理由により、現在の JSON エコシステムに自然な位置を持たない数値は、 JSON 文字列型を使用してラップされなければならない(MUST)。 これはオープンシステムにおける事実上の標準に近い。これは、 Appendix E に記述される "DateTime" オブジェクトのように、JSON で直接サポートされない他のデータ型にも適用される。

JSON 文字列型を使用するシステムの助けを借りれば、次のようなプログラム的な方法であれ

  var obj = JSON.parse('{"giantNumber": "1.4e+9999"}');
  var biggie = new BigNumber(obj.giantNumber);

OpenAPI [OPENAPI] のような宣言的スキームであれ、 JCS は、ECMAScript を使用する場合を含め、アプリケーションに制限を課さない。

Appendix E. 文字列サブタイプの扱い

JSON に備わるデータ型の集合が限られているため、JSON 文字列型は サブタイプを保持するために一般的に使用される。これは JSON の解析方法によっては 相互運用性の問題につながる可能性があり、より広い読者層を対象とする JCS 適合アプリケーションでは対処されなければならない(MUST)。

スキーマ設計者が "BigInt" サブタイプを保持するためのプロパティとして "big" を割り当て、"DateTime" サブタイプを保持するためのプロパティとして "time" を割り当て、一方で "val" は JCS に適合する JSON 数値であると 想定される JSON オブジェクトを解析したいと仮定する。次の例はそのような オブジェクトを示している。

  {
    "time": "2019-01-28T07:45:10Z",
    "big": "055",
    "val": 3.5
  }

このオブジェクトの解析は、次の ECMAScript 文で実現できる。

  var object = JSON.parse(JSON_object_featured_as_a_string);

解析後、実際のデータを抽出できる。サブタイプについては、 解析処理の結果(ECMAScript オブジェクト)を入力として使用する変換手順も 含まれる。

  ... = new Date(object.time); // Date object
  ... = BigInt(object.big);    // Big integer
  ... = object.val;            // JSON/JS number

"BigInt" データ型は、現在 V8 [V8] によってのみ ネイティブにサポートされていることに注意。

Appendix A のサンプルコードを使用して "object" を正準化すると、 次の文字列が返される。

  {"big":"055","time":"2019-01-28T07:45:10Z","val":3.5}

これは(JCS に関して)技術的には正しいが、 JSON データを解析する別の方法があり、以下に示すように ECMAScript と併用することもできる。

  // "BigInt" requires the following code to become JSON serializable
  BigInt.prototype.toJSON = function() {
      return this.toString();
  };

  // JSON parsing using a "stream"-based method
  var object = JSON.parse(JSON_object_featured_as_a_string,
      (k,v) => k == 'time' ? new Date(v) : k == 'big' ? BigInt(v) : v
  );

ここで Appendix A の正準化器を "object" に適用すると、次の文字列が生成される。

  {"big":"55","time":"2019-01-28T07:45:10.000Z","val":3.5}

この場合、"big" および "time" の文字列引数は元のものから変化しており、 JCS に依存するアプリケーションが失敗する原因になると考えられる。

逸脱の理由は、ストリームベースおよびスキーマベースの JSON パーサーでは、 元の文字列引数が通常、その場でネイティブサブタイプに置き換えられ、 それがシリアル化されると、異なる、かつプラットフォーム依存のパターンを 示す可能性があるためである。

すなわち、ストリームベースおよびスキーマベースの解析は、 サブタイプを「純粋な」(不変の)JSON 文字列型として扱い、 実際の指定されたネイティブ型への変換を後続の手順で行わなければならない (MUST)。Go、Java、および C# のような現代の プログラミングプラットフォームでは、アノテーション、ゲッター、および セッターを組み合わせることで、適度な労力でこれを実現できる。 以下は、JSON オブジェクトとしてシリアル化可能なクラスの一部を示す C#/Json.NET の例である。

  // The "pure" string solution uses a local
  // string variable for JSON serialization while
  // exposing another type to the application
  [JsonProperty("amount")]
  private string _amount;

  [JsonIgnore]
  public decimal Amount {
      get { return decimal.Parse(_amount); }
      set { _amount = value.ToString(); }
  }

アプリケーション内では、"Amount" は他のプロパティと同じようにアクセスできるが、 JSON 文脈では実際には引用符付き文字列によって表される。

注: 上記の例は、I-JSON によって暗示される数値データの制約にも対処している (C# の "decimal" データ型は、IEEE 754 二倍精度と比較してかなり異なる特性を持つ)。

E.1. 配列内のサブタイプ

JSON 配列構造は任意の JSON データ型の混在を許すため、 いずれにせよサブタイプに対処するためのカスタム解析およびシリアル化コードが 必要になる場合がある。

Appendix F. 実装ガイドライン

最適な解決策は、JCS のサポートを JSON シリアライザーに直接統合することである (パーサーに変更は不要である)。 すなわち、正準化は JSON シリアライザーの追加の "mode" にすぎなくなる。 しかし、現時点ではこれは当てはまらない。幸い、JCS サポートは、 既存の JSON シリアライザーに対するポストプロセッサとして動作する、 外部から提供される正準化器ソフトウェアを通じて導入できる。 この構成はまた、JCS 実装者が、基礎となるデータを JSON で どのように表すべきかを扱う必要を軽減する。

ポストプロセッサの概念は、次のような署名作成スキームを可能にする。

  1. 署名されるデータを作成する。
  2. 既存の JSON ツールを使用してデータをシリアル化する。
  3. 外部正準化器にシリアル化されたデータを処理させ、 正準化された結果データを返させる。
  4. 正準化されたデータに署名する。
  5. 指定された署名プロパティを通じて、結果として得られる署名値を 元の JSON データに追加する。
  6. 完成した(現在署名済みの)JSON オブジェクトを既存の JSON ツールを使用してシリアル化する。

互換性のある署名検証スキームは、次のようになる。

  1. 既存の JSON ツールを使用して、署名済み JSON データを解析する。
  2. 指定された署名プロパティから署名値を読み取り、保存する。
  3. 解析された JSON オブジェクトから署名プロパティを削除する。
  4. 残りの JSON データを既存の JSON ツールを使用してシリアル化する。
  5. 外部正準化器にシリアル化されたデータを処理させ、 正準化された結果データを返させる。
  6. 署名作成に使用されたアルゴリズムおよび鍵を使用して、 正準化されたデータが保存された署名値と一致することを検証する。

上記のような正準化器は、実質的には「フィルター」にすぎず、 多数のかなり異なる暗号スキームで使用できる可能性がある。

JCS サポートが統合された JSON シリアライザーを使用すると、 両方の処理において、正準化手順の前に行われるシリアル化を 省略できる。

Appendix G. オープンソース 実装

次のオープンソース実装は、JCS と互換性があることが検証されている。

Appendix H. その他の JSON 正準化の 取り組み

"Canonical JSON" を作成する他の取り組みが存在する(または存在した)。 以下はその一部の URL のリストである。

掲載された取り組みはいずれも、テキストレベルの JSON-to-JSON 変換を 基盤としている。テキストレベル正準化の主な特徴は、 使用される JSON の種類に対して中立にできることである。 しかし、そのようなスキームは JSON 解析処理への大きな変更も意味し、 これは採用への障害となる可能性が高い。特定の JSON および アプリケーション制約を代償としつつも、JCS は既存の JSON ツールと 互換性を持つように設計された。

Appendix I. 開発ポータル

JCS 仕様は現在、次で開発されている。 <https://github.com/cyberphone/ietf-json-canon>

JCS ソースコードおよび広範なテストデータは次で利用可能である。 <https://github.com/cyberphone/json-canonicalization>

謝辞

ECMAScript 数値シリアル化を基盤とすることは、 もともと James Manger によって提案された。 これは最終的に、JSON プリミティブのための ECMAScript シリアル化スキーム全体の 採用につながった。

本仕様に貴重な入力を提供した他の人々には、 Scott Ananian, Tim Bray, Ben Campbell, Adrian Farell, Richard Gibson, Bron Gondwana, John-Mark Gurney, Mike Jones, John Levine, Mark Miller, Matthew Miller, Mark Nottingham, Mike Samuel, Jim Schaad, Robert Tupelo-Schneck, および Michal Wadas が含まれる。

実世界での概念検証を行うにあたり、 Ulf Adams, Tanner Gooding, および Remy Oudompheng が提供した 数値シリアル化のためのソフトウェアとサポートは非常に有用であった。

著者の連絡先

Anders Rundgren
Independent
Montpellier
France
Bret Jordan
Broadcom
1320 Ridder Park Drive
San Jose, CA 95131
United States of America
Samuel Erdtman
Spotify AB
Birger Jarlsgatan 61, 4tr
SE-113 56 Stockholm
Sweden