RFC 8785
JSON Canonicalization Scheme (JCS)
RFC 8785: JSON Canonicalization Scheme (JCS) ๐
Okay bestie, this one is RFC 8785, all about the JSON Canonicalization Scheme (JCS) ๐. Here's the title block tea โ:
- Stream: Independent Submission ๐ฆ
- Category: Informational (so it's not an Internet Standards Track spec, just published for info) โจ
- Authors: A. Rundgren (Independent), B. Jordan (Broadcom), and S. Erdtman (Spotify AB) ๐
- Date: June 2020 ๐ธ
- ISSN: 2070-1721
- Obsoletes / Updates: the title block doesn't list anything it obsoletes or updates ๐
Abstract
Okay so, hashing and signing need the data to look exactly the same every single time, or the results won't match up ๐ญ. One fix is to make a canonical version of the data. The cute part is that you can still send the data in its original form over the "wire" ๐ง, and the producer and consumer can each canonicalize their own copy and still get consistent results. It's giving reliability ๐ .
This document describes the JSON Canonicalization Scheme (JCS) ๐. It explains how to build a canonical version of JSON data by leaning on the strict serialization rules for JSON primitives that ECMAScript already defines, by keeping the data inside the Internet JSON (I-JSON) subset, and by sorting object properties in a deterministic way โจ.
Status of This Memo
Heads up: this document is not an Internet Standards Track spec ๐. It's published purely for information ๐.
This is a contribution to the RFC Series, independent of any other RFC stream ๐ฆ. The RFC Editor picked to publish it at their discretion and makes no statement about how valuable it is for implementation or deployment ๐ . Documents the RFC Editor approves for publication are not candidates for any level of Internet Standard, see Section 2 of RFC 7841 ๐.
For info on the current status, any errata, and how to give feedback, check https://www.rfc-editor.org/info/rfc8785 ๐ฅ.
Copyright Notice
Copyright (c) 2020 IETF Trust and the persons identified as the document authors. All rights reserved ๐.
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) as in effect on the date of publication. Please read these carefully, because they spell out your rights and restrictions for this document ๐ซถ.
Table of Contents
1. Introduction
2. Terminology
3. Detailed Operation
3.1. Creation of Input Data
3.2. Generation of Canonical JSON Data
3.2.1. Whitespace
3.2.2. Serialization of Primitive Data Types
3.2.2.1. Serialization of Literals
3.2.2.2. Serialization of Strings
3.2.2.3. Serialization of Numbers
3.2.3. Sorting of Object Properties
3.2.4. UTF-8 Generation
4. IANA Considerations
5. Security Considerations
6. References
6.1. Normative References
6.2. Informative References
Appendix A. ECMAScript Sample Canonicalizer
Appendix B. Number Serialization Samples
Appendix C. Canonicalized JSON as "Wire Format"
Appendix D. Dealing with Big Numbers
Appendix E. String Subtype Handling
E.1. Subtypes in Arrays
Appendix F. Implementation Guidelines
Appendix G. Open-Source Implementations
Appendix H. Other JSON Canonicalization Efforts
Appendix I. Development Portal
Acknowledgements
Authors' Addresses
1. Introduction โจ
Alright, so this document is all about the JSON Canonicalization Scheme (JCS) ๐. It defines how to create a canonical representation of JSON [RFC8259] data by building on the strict serialization methods for JSON primitives that ECMAScript [ECMA-262] defines, by keeping the data inside the I-JSON [RFC7493] subset, and by using deterministic property sorting ๐ง. The output is a "hashable" representation of JSON data that cryptographic methods can use ๐. The paragraphs after this one walk through the main design considerations ๐.
Cryptographic operations like hashing and signing need the data to be in an invariant format so they're reliably repeatable ๐ญ. One way to get there is to turn the data into a format with a simple, fixed representation, like base64url [RFC4648] ๐ธ. That's how JSON Web Signature (JWS) [RFC7515] handled it. Another option is to make a canonical version of the data, which is what the XML signature [XMLDSIG] standard did ๐ .
The big advantage of a canonicalizing scheme is that the data can stay in its original form, and that's the whole core idea behind JCS ๐ฆ. In other words, canonicalization lets a JSON object stay a JSON object even after it's signed ๐ฅ. That makes system design, documentation, and logging way simpler ๐ซถ.
To avoid "reinventing the wheel" ๐, JCS relies on the serialization of JSON primitives (strings, numbers, and literals) as ECMAScript (aka JavaScript) [ECMA-262] defines them, starting with version 6 ๐.
Seasoned XML developers might remember the pain of getting XML signatures to validate ๐ต. That usually came down to different readings of the super intricate XML canonicalization rules, plus the equally complicated Web Services security standards ๐. JCS should avoid those same problems, and here's why:
- JSON doesn't have a namespace concept or default values ๐.
- The data is constrained to the I-JSON [RFC7493] subset. That gets rid of the need for special parsers just to handle canonicalization ๐.
- JCS-compatible serialization of JSON primitives is already supported by most web browsers and by Node.js [NODEJS] ๐ธ.
- The full JCS spec is supported by multiple open-source implementations (see Appendix G) โจ. Appendix F also has implementation guidelines ๐.
JCS is compatible with some existing systems that rely on JSON canonicalization, like the JSON Web Key (JWK) Thumbprint [RFC7638] and Keybase [KEYBASE] ๐.
If you're curious about uses outside of cryptography, check out [JSONCOMP] ๐ง.
This document is aimed at JSON tool vendors and at designers of JSON-based cryptographic solutions ๐ . The reader is assumed to know ECMAScript well, including the "JSON" object ๐ค.
2. Terminology ๐
Note that this document is not on the IETF standards track ๐. Even so, a conformant implementation is supposed to stick to the specified behavior, for security and interoperability reasons ๐. This text uses BCP 14 to describe that necessary behavior ๐.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here ๐.
3. Detailed Operation ๐ ๏ธ
This section covers the details of creating a canonical JSON representation and how JCS handles them ๐ง.
Appendix F describes the RECOMMENDED way of adding JCS support to existing JSON tools ๐.
3.1. Creation of Input Data ๐ฅ
Data that's going to be canonically serialized usually comes from one of two places ๐ฆ:
- Parsing previously generated JSON data ๐.
- Programmatically creating the data ๐ค.
No matter which method you use, the data to be serialized MUST be adapted for I-JSON [RFC7493] formatting ๐ . In practice that looks like this:
- JSON objects MUST NOT exhibit duplicate property names ๐ .
- JSON string data MUST be expressible as Unicode [UNICODE] ๐.
- JSON number data MUST be expressible as IEEE 754 [IEEE754] double-precision values ๐ข. If an application needs more precision or longer integers than IEEE 754 double precision offers, it is RECOMMENDED to represent those numbers as JSON strings. Appendix D has the details on how to do that in an interoperable and extensible way ๐ง.
There's one more constraint: parsed JSON string data MUST NOT be altered during subsequent serializations ๐. For more on that, see Appendix E ๐.
Note: Unicode has a feature where certain character sequences can get rearranged, called "Unicode Normalization" [UCNORM] ๐. JCS-compliant string processing doesn't take that into account. So every component involved in a scheme that depends on JCS MUST preserve Unicode string data "as is" ๐ซถ.
3.2. Generation of Canonical JSON Data ๐ง
The subsections below walk you through the steps to create a canonical JSON representation of the data from the previous section โจ.
Appendix A has sample code for an ECMAScript-based canonicalizer that matches the JCS spec ๐ป.
3.2.1. Whitespace โฌ
Whitespace between JSON tokens MUST NOT be emitted ๐ซ.
3.2.2. Serialization of Primitive Data Types ๐ค
Okay, picture this JSON object getting parsed ๐:
{
"numbers": [333333333.33333329, 1E30, 4.50,
2e-3, 0.000000000000000000000000001],
"string": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/",
"literals": [null, true, false]
}
If that parsed data then gets serialized with a serializer that's compliant with ECMAScript's "JSON.stringify()", the result would be (with a line wrap added just for display purposes) pretty different from the original ๐ณ:
{"numbers":[333333333.3333333,1e+30,4.5,0.002,1e-27],"string":
"โฌ$\u000f\nA'B\"\\\\\"/","literals":[null,true,false]}
Why the difference? JSON [RFC8259] is very tolerant about input data, but output data (as ECMAScript defines it) has one fixed representation ๐ฏ. As the example shows, numbers get rounded too ๐.
The subsections below describe how JCS serializes primitive JSON data types. This part is identical to ECMAScript's version ๐ช. In the (unlikely) event that a future ECMAScript version invalidates any of these serialization methods, it'll be up to the developer community to either stick with this specification or write a new one ๐ฎ.
3.2.2.1. Serialization of Literals ๐งธ
In accordance with JSON [RFC8259], the literals "null", "true", and "false" MUST be serialized as null, true, and false, respectively ๐.
3.2.2.2. Serialization of Strings ๐
For JSON string data (which includes JSON object property names too), each Unicode code point MUST be serialized as described below (see Section 24.3.2.2 of [ECMA-262]) ๐ :
- If the Unicode value falls within the traditional ASCII control character range (U+0000 through U+001F), it MUST be serialized using lowercase hexadecimal Unicode notation (
\uhhhh) unless it's one of the predefined JSON control characters U+0008, U+0009, U+000A, U+000C, or U+000D. Those MUST be serialized as\b,\t,\n,\f, and\r, respectively ๐ธ. - If the Unicode value is outside the ASCII control character range, it MUST be serialized "as is", unless it's equivalent to U+005C (
\) or U+0022 ("). Those MUST be serialized as\\and\", respectively ๐ฆ.
Finally, the resulting sequence of Unicode code points MUST be enclosed in double quotes (") ๐ซถ.
Note: Invalid Unicode data like "lone surrogates" (e.g., U+DEAD) can cause interoperability problems, including broken signatures ๐. So occurrences of such data MUST cause a compliant JCS implementation to terminate with an appropriate error ๐จ.
3.2.2.3. Serialization of Numbers
okay so numbers are where it gets spicy ๐ถ๏ธ ECMAScript is built on the IEEE 754 [IEEE754] double-precision standard for representing JSON number data, so that data MUST be serialized according to Section 7.1.12.1 of [ECMA-262], including the "Note 2" enhancement ๐ฅ
The algorithm itself isn't spelled out in this document, since it's pretty complicated ๐ If you're implementing JCS-compliant number serialization, Google's implementation in V8 [V8] can be a reference. Ryu [RYU] is another compatible reference implementation, and it's what the JCS open-source Java implementation in Appendix G uses. Appendix B has a set of IEEE 754 sample values with their matching JSON serialization โจ
Note: Since Not a Number (NaN) and Infinity are not permitted in JSON, occurrences of NaN or Infinity MUST cause a compliant JCS implementation to terminate with an appropriate error. ๐
3.2.3. Sorting of Object Properties
so even after the last step cleaned up the primitive types, it still wouldn't count as "canonical" ๐ญ the reason is that JSON object properties aren't in lexicographic (alphabetical) order yet
Using the sample from Section 3.2.2, a properly canonicalized version should (with a line wrap added for display purposes only) read as:
{"literals":[null,true,false],"numbers":[333333333.3333333,
1e+30,4.5,0.002,1e-27],"string":"โฌ$\u000f\nA'B\"\\\\\"/"}
here are the rules for sorting JSON object properties under JCS, bestie ๐
- JSON object properties MUST be sorted recursively, which means that JSON child Objects MUST have their properties sorted as well.
- JSON array data MUST also be scanned for the presence of JSON objects (if an object is found, then its properties MUST be sorted), but array element order MUST NOT be changed.
when a JSON object is about to get its properties sorted, these measures MUST be adhered to ๐
- The sorting process is applied to property name strings in their "raw" (unescaped) form. That is, a newline character is treated as U+000A.
- Property name strings are formatted as arrays of UTF-16 [UNICODE] code units. The sorting is based on pure value comparisons, where code units are treated as unsigned integers, independent of locale settings.
- Property name strings either have different values at some index that is a valid index for both strings, or their lengths are different, or both. If they have different values at one or more index positions, let k be the smallest such index; then, the string whose value at position k has the smaller value, as determined by using the "<" operator, lexicographically precedes the other string. If there is no index position at which they differ, then the shorter string lexicographically precedes the longer string.
in plain English, property names get sorted in ascending order like this ๐ฆ
""
"a"
"aa"
"ab"
the reason the sorting is based on UTF-16 code units is that it maps directly onto the string type in ECMAScript (the stuff in web browsers and Node.js), Java, and .NET ๐ JSON also only supports escape sequences written as UTF-16 code units, so handling that stuff is kind of a must-know anyway. Systems that store string data some other way will need to convert JSON property name strings into arrays of UTF-16 code units before sorting. That conversion from UTF-8 or UTF-32 to UTF-16 is defined by the Unicode [UNICODE] standard ๐
to check that your JCS sorting is correct, you can use this JSON test data ๐ง
{
"\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"
}
and this is the expected argument order after the property strings are sorted ๐
"Carriage Return"
"One"
"Control"
"Latin Small Letter O With Diaeresis"
"Euro Sign"
"Emoji: Grinning Face"
"Hebrew Letter Dalet With Dagesh"
Note: For the purpose of obtaining a deterministic property order, sorting of data encoded in UTF-8 or UTF-32 would also work, but the outcome for JSON data like above would differ and thus be incompatible with this specification. However, in practice, property names are rarely defined outside of 7-bit ASCII, making it possible to sort string data in UTF-8 or UTF-32 format without conversion to UTF-16 and still be compatible with JCS. Whether or not this is a viable option depends on the environment JCS is used in. ๐
3.2.4. UTF-8 Generation
last step! ๐ to get a platform-independent representation, the result of the previous step MUST be encoded in UTF-8 ๐ซถ
applied to the sample in Section 3.2.3, this should give you the following bytes (shown in hexadecimal) ๐
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
this data is meant to be used as input to cryptographic methods ๐
4. IANA Considerations โจ
this document has no IANA actions, so nothing to see here for the registry folks ๐
5. Security Considerations ๐
okay, this section is SERIOUS ๐ญ sanity checks on input data are crucial, otherwise you could end up overflowing buffers and messing with the integrity of the whole system. it's giving disaster movie ๐
when JCS is applied to signature schemes like the one in Appendix F, applications MUST perform the following operations before acting upon received data:
- Parse the JSON data and verify that it adheres to I-JSON.
- Verify the data for correctness according to the conventions defined by the ecosystem where it is to be used. This also includes locating the property holding the signature data.
- Verify the signature.
if any of these steps fail, the operation in progress MUST be aborted. no exceptions, periodt ๐
6. References ๐
6.1. Normative References
- [ECMA-262] ECMA International, "ECMAScript 2019 Language Specification", Standard ECMA-262 10th Edition, June 2019, <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, March 1997, <https://www.rfc-editor.org/info/rfc2119>.
- [RFC7493] Bray, T., Ed., "The I-JSON Message Format", RFC 7493, DOI 10.17487/RFC7493, March 2015, <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, May 2017, <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, December 2017, <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. Informative References
- [JSONCOMP] Rundgren, A., ""Comparable" JSON (JSONCOMP)", Work in Progress, Internet-Draft, draft-rundgren-comparable-json-04, 13 February 2019, <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, October 2006, <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, May 2015, <https://www.rfc-editor.org/info/rfc7515>.
- [RFC7638] Jones, M. and N. Sakimura, "JSON Web Key (JWK) Thumbprint", RFC 7638, DOI 10.17487/RFC7638, September 2015, <https://www.rfc-editor.org/info/rfc7638>.
- [RYU] "Ryu floating point number serializing algorithm", commit 27d3c55, May 2020, <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, April 2013, <https://www.w3.org/TR/xmldsig-core1/>.
Appendix A. ECMAScript Sample Canonicalizer โจ
Okay bestie, here's a little ECMAScript canonicalizer so you can see the JCS core in action ๐ The code is only there to highlight the algorithm, so error handling and UTF-8 generation were skipped on purpose ๐ซถ
////////////////////////////////////////////////////////////
// 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. Number Serialization Samples ๐
Here's a table of ECMAScript-compatible number serialization samples, edge cases included ๐ง The "IEEE 754" column is the internal ECMAScript representation of the "Number" data type. That's based on the IEEE 754 [IEEE754] standard using 64-bit (double-precision) values, shown here in hexadecimal.
+==================+===========================+====================+
| IEEE 754 | JSON Representation | Comment |
+==================+===========================+====================+
| 0000000000000000 | 0 | Zero |
+------------------+---------------------------+--------------------+
| 8000000000000000 | 0 | Minus zero |
+------------------+---------------------------+--------------------+
| 0000000000000001 | 5e-324 | Min pos number |
+------------------+---------------------------+--------------------+
| 8000000000000001 | -5e-324 | Min neg number |
+------------------+---------------------------+--------------------+
| 7fefffffffffffff | 1.7976931348623157e+308 | Max pos number |
+------------------+---------------------------+--------------------+
| ffefffffffffffff | -1.7976931348623157e+308 | Max neg number |
+------------------+---------------------------+--------------------+
| 4340000000000000 | 9007199254740992 | Max pos int (1) |
+------------------+---------------------------+--------------------+
| c340000000000000 | -9007199254740992 | Max neg 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 | Round to even (4) |
+------------------+---------------------------+--------------------+
Table 1: ECMAScript-Compatible JSON Number Serialization Samples
Notes:
(1) Okay so for the most compliance with the ECMAScript "JSON" object, values that are meant to be treated as true integers SHOULD be in the range -9007199254740991 to 9007199254740991. But how you use numbers in your app doesn't change how the JCS algorithm works ๐
(2) Even though a set of specific integers like 2**68 could be seen as having extended precision, the JCS/ECMAScript number serialization algorithm doesn't take that into account ๐
(3) Values out of range are not permitted in JSON. See Section 3.2.2.3 ๐ซ
(4) This number is exactly 1424953923781206.25, but after the "Note 2" rule from Section 3.2.2.3 it gets truncated and rounded to the closest even value ๐
For a more exhaustive validation of a JCS number serializer, you may test against a file (currently) available in the development portal (see Appendix I) that has a big set of sample values ๐ Another option is running V8 [V8] as a live reference alongside a program that generates a ton of random IEEE 754 values ๐
Appendix C. Canonicalized JSON as "Wire Format" ๐ฆ
Since the result of the canonicalization process (see Section 3.2.4) is fully valid JSON, it can also be used as "Wire Format" โจ It's just an option though, because cryptographic schemes based on JCS would, in most cases, not depend on the externally supplied JSON data already being canonicalized ๐ซถ
Honestly, the ECMAScript standard way of serializing objects with "JSON.stringify()" gives you a more "logical" format, where properties stay in the order they were created or received ๐ Here's an address record that could benefit from that standard serialization:
{
"name": "John Doe",
"address": "2000 Sunset Boulevard",
"city": "Los Angeles",
"zip": "90001",
"state": "CA"
}
Using canonicalization, the properties above would come out in the order "address", "city", "name", "state", and "zip". That makes the data a bit fuzzy for a human (developer or tech support) to read ๐ญ Canonicalization also squishes the JSON data into a single line of text, which may be less than ideal for debugging and logging ๐
Appendix D. Dealing with Big Numbers ๐
There are a few issues with the JSON number type, and they're all shown in this sample object ๐ธ
{
"giantNumber": 1.4e+9999,
"payMeThis": 26000.33,
"int64Max": 9223372036854775807
}
Even though this sample conforms to JSON [RFC8259], applications would normally use different native data types for storing "giantNumber" and "int64Max" ๐ง Also, monetary data like "payMeThis" would presumably not rely on floating-point data types, because of rounding issues with decimal arithmetic ๐ธ
The established way of handling this kind of "overloading" of the JSON number type (at least in an extensible way) is through mapping mechanisms, which tell parsers what to do with different properties based on their name. But that greatly limits the value of using the JSON number type outside its original, somewhat constrained JavaScript context ๐ฌ The ECMAScript "JSON" object doesn't support mappings to the JSON number type either.
So, because of all that, numbers that don't have a natural place in the current JSON ecosystem MUST be wrapped using the JSON string type ๐ This is pretty much a de facto standard for open systems โจ It also applies to other data types that JSON doesn't directly support, like "DateTime" objects described in Appendix E ๐
Using a system that uses the JSON string type, whether programmatic like
var obj = JSON.parse('{"giantNumber": "1.4e+9999"}');
var biggie = new BigNumber(obj.giantNumber);
or declarative schemes like OpenAPI [OPENAPI], JCS puts no limits on applications, including when they use ECMAScript ๐ฅฐ
Appendix E. String Subtype Handling โจ
Okay so JSON only gives you a few data types ๐ , which is why people keep stuffing subtypes (dates, big numbers, that kind of thing) into plain JSON strings ๐. Lowkey genius, until it isn't. Depending on how you parse the JSON, this can cause interoperability problems ๐ญ. Any JCS-compliant app that wants to reach a wider audience MUST deal with this.
Let's say the schema designer gave the property "big" a "BigInt" subtype and "time" a "DateTime" subtype, and "val" is supposed to be a JCS-compliant JSON number. Here's the object:
{
"time": "2019-01-28T07:45:10Z",
"big": "055",
"val": 3.5
}
To parse it, you'd use this ECMAScript statement ๐ง:
var object = JSON.parse(JSON_object_featured_as_a_string);
Then you pull out the actual data. For subtypes, you also have to convert the parsed result (an ECMAScript object) into the real type ๐ฆ:
... = new Date(object.time); // Date object
... = BigInt(object.big); // Big integer
... = object.val; // JSON/JS number
Heads up ๐: the "BigInt" data type is currently only natively supported by V8 [V8].
If you canonicalize "object" with the sample code in Appendix A, you get this string ๐:
{"big":"055","time":"2019-01-28T07:45:10Z","val":3.5}
Technically correct with respect to JCS, but there's another way to parse JSON data, and it works with ECMAScript too ๐. Check this out:
// "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
);
Run the canonicalizer from Appendix A on this "object" and you get this instead ๐:
{"big":"55","time":"2019-01-28T07:45:10.000Z","val":3.5}
Bestie, look at those strings. The arguments for "big" and "time" have changed from the original, and an application that depends on JCS would presumably fail ๐ญ.
The reason is that stream- and schema-based JSON parsers usually swap the original string argument on the fly for the native subtype, and when that gets serialized again, it can follow a different, platform-dependent pattern ๐.
So here's the rule ๐ซถ: stream- and schema-based parsing MUST treat subtypes as "pure" (immutable) JSON string types, and do the actual conversion to the designated native type in a separate, later step. In modern platforms like Go, Java, and C#, you can get this done with moderate effort by combining annotations, getters, and setters โจ. Here's a snippet in C#/Json.NET showing part of a class that serializes as a JSON object:
// 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(); }
}
In the app, "Amount" is accessed like any other property, but in JSON contexts it's actually a quoted string ๐.
Note: The example above also covers the numeric constraints implied by I-JSON. The C# "decimal" data type is pretty different from IEEE 754 double precision, so it's a whole vibe ๐.
E.1. Subtypes in Arrays
Since the JSON array construct lets you mix any JSON data types you want, you may end up needing custom parsing and serialization code to handle subtypes anyway ๐ .
Appendix F. Implementation Guidelines ๐ง
The dream scenario is building JCS support straight into JSON serializers (parsers need no changes). Canonicalization would just be another "mode" for a JSON serializer ๐ธ. Sadly, that's not the case yet. The good news is that JCS support can be added through externally supplied canonicalizer software that acts as a post processor on top of existing JSON serializers โจ. This setup also means the JCS implementer doesn't have to worry about how the underlying data is represented in JSON ๐.
The post processor idea makes signature schemes like this one possible ๐ฅ:
- Create the data to be signed.
- Serialize the data using existing JSON tools.
- Let the external canonicalizer process the serialized data and return canonicalized result data.
- Sign the canonicalized data.
- Add the resulting signature value to the original JSON data through a designated signature property.
- Serialize the completed (now signed) JSON object using existing JSON tools.
A matching verification scheme goes like this ๐ฆ:
- Parse the signed JSON data using existing JSON tools.
- Read and save the signature value from the designated signature property.
- Remove the signature property from the parsed JSON object.
- Serialize the remaining JSON data using existing JSON tools.
- Let the external canonicalizer process the serialized data and return canonicalized result data.
- Verify that the canonicalized data matches the saved signature value using the algorithm and key used for creating the signature.
A canonicalizer like this is basically just a "filter" ๐งน, and it could work with a bunch of very different cryptographic schemes ๐.
If your JSON serializer has JCS support built in, you can drop the serialization step before canonicalization for both processes ๐.
Appendix G. Open-Source Implementations ๐
These open-source implementations have been verified to be compatible with JCS ๐ซถ:
- JavaScript:
https://www.npmjs.com/package/canonicalize - Java:
https://github.com/erdtman/java-json-canonicalization - Go:
https://github.com/cyberphone/json-canonicalization/tree/master/go - .NET/C#:
https://github.com/cyberphone/json-canonicalization/tree/master/dotnet - Python:
https://github.com/cyberphone/json-canonicalization/tree/master/python3
Appendix H. Other JSON Canonicalization Efforts ๐
There are (and have been) other efforts to create "Canonical JSON" ๐ท. Here's a list of URLs for some of them:
https://tools.ietf.org/html/draft-staykov-hu-json-canonical-form-00https://gibson042.github.io/canonicaljson-spec/http://wiki.laptop.org/go/Canonical_JSON
Every one of these builds on text-level JSON-to-JSON transformations. The big perk of text-level canonicalization is that it can be neutral to the flavor of JSON you're using ๐ฆ. But these schemes also mean major changes to the JSON parsing process, which is probably the biggest hurdle to adoption ๐ฌ. JCS was designed to work with existing JSON tools, at the cost of some JSON and application constraints ๐ .
Appendix I. Development Portal ๐
The JCS specification is being developed right now at https://github.com/cyberphone/ietf-json-canon ๐ ๏ธ.
JCS source code and a ton of test data live at https://github.com/cyberphone/json-canonicalization ๐.
Acknowledgements ๐ซถ
Building on ECMAScript number serialization was originally proposed by James Manger ๐. That idea eventually led to adopting the entire ECMAScript serialization scheme for JSON primitives โจ.
Other people who gave valuable input to this specification include 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, and Michal Wadas ๐.
For real-world concept verification, the software and support for number serialization from Ulf Adams, Tanner Gooding, and Remy Oudompheng was super helpful ๐ง.
Authors' Addresses ๐ฆ
Anders Rundgren
Independent
Montpellier
France
Email: anders.rundgren.net@gmail.com
URI: https://www.linkedin.com/in/andersrundgren/
Bret Jordan
Broadcom
1320 Ridder Park Drive
San Jose, CA 95131
United States of America
Email: bret.jordan@broadcom.com
Samuel Erdtman
Spotify AB
Birger Jarlsgatan 61, 4tr
SE-113 56 Stockholm
Sweden
Email: erdtman@spotify.com
That's the whole spec, bestie! ๐โจ JSON Canonicalization, done and dusted ๐ ๐
๐ฆ where this page comes from
- ietf: rfc8785 2020-06
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms