diff --git a/draft-ietf-oauth-status-list.md b/draft-ietf-oauth-status-list.md index 84b88a2..14b266b 100644 --- a/draft-ietf-oauth-status-list.md +++ b/draft-ietf-oauth-status-list.md @@ -249,7 +249,7 @@ A Status List is a byte array that contains the statuses of many Referenced Toke 3. The byte array is compressed using DEFLATE {{RFC1951}} with the ZLIB {{RFC1950}} data format. Implementations are RECOMMENDED to use the highest compression level available. -The following example illustrates a Status List that represents the statuses of 16 Referenced Tokens, requiring 16 bits (2 bytes) for the uncompressed byte array: +The following example illustrates a Status List that represents the statuses of 16 Referenced Tokens, requiring 16 bits (2 bytes) for the uncompressed byte array (1 bit status): ~~~ ascii-art @@ -286,6 +286,43 @@ index 7 6 5 4 3 2 1 0 15 ... 10 9 8 23 ~~~ +In this example, the Status List additionally includes the Status Type "SUSPENDED". As the Status Type value for "SUSPENDED" is 0x02 and does not fit into 1 bit, the "bits" is required to be 2. + +This example Status List represents the status of 12 Referenced Tokens, requiring 24 bits (3 bytes) of status (2 bit status): + +~~~ ascii-art + +status[0] = 1 +status[1] = 2 +status[2] = 0 +status[3] = 3 +status[4] = 0 +status[5] = 1 +status[6] = 0 +status[7] = 1 +status[8] = 1 +status[9] = 2 +status[10] = 3 +status[11] = 3 +~~~ + +These bits are concatenated: + +~~~ ascii-art + +byte 0 1 2 +bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +values |1|1|0|0|1|0|0|1| |0|1|0|0|0|1|0|0| |1|1|1|1|1|0|0|1| + +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ + \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / +status 3 0 2 1 1 0 1 0 3 3 2 1 +index 3 2 1 0 7 6 5 4 11 10 9 8 + \___________/ \___________/ \___________/ + 0xC9 0x44 0xF9 + +~~~ + ## Status List in JSON Format {#status-list-json} This section defines the data structure for a JSON-encoded Status List: @@ -295,12 +332,20 @@ This section defines the data structure for a JSON-encoded Status List: * `lst`: REQUIRED. JSON String that contains the status values for all the Referenced Tokens it conveys statuses for. The value MUST be the base64url-encoded Status List as specified in [](#status-list). * `aggregation_uri`: OPTIONAL. JSON String that contains a URI to retrieve the Status List Aggregation for this type of Referenced Token. See section [](#aggregation) for further details. -The following example illustrates the JSON representation of the Status List: +The following example illustrates the JSON representation of the Status List with bit-size 1 from the example above: ~~~~~~~~~~ {::include ./examples/status_list_encoding_json} ~~~~~~~~~~ +The following example illustrates the JSON representation of the Status List with bit-size 2 from the example above: + +~~~~~~~~~~ +{::include ./examples/status_list_encoding2_json} +~~~~~~~~~~ + +See section [](#test-vectors) for more test vectors. + ## Status List in CBOR Format {#status-list-cbor} This section defines the data structure for a CBOR-encoded Status List: @@ -322,6 +367,8 @@ The following is the CBOR Annotated Hex output of the example above: {::include ./examples/status_list_encoding_cbor_diag} ~~~~~~~~~~ +See section [](#test-vectors) for more test vectors. + # Status List Token {#status-list-token} A Status List Token embeds the Status List into a token that is cryptographically signed and protects the integrity of the Status List. This allows for the Status List Token to be hosted by third parties or be transferred for offline use cases. @@ -808,53 +855,6 @@ The following is a non-normative example for media type `application/json`: } ~~~ -# Further Examples - -## Status List with 2-Bit Status Values in JSON format - -In this example, the Status List additionally includes the Status Type "SUSPENDED". As the Status Type value for "SUSPENDED" is 0x02 and does not fit into 1 bit, the "bits" is required to be 2. - -This example Status List represents the status of 12 Referenced Tokens, requiring 24 bits (3 bytes) of status. - -~~~ ascii-art - -status[0] = 1 -status[1] = 2 -status[2] = 0 -status[3] = 3 -status[4] = 0 -status[5] = 1 -status[6] = 0 -status[7] = 1 -status[8] = 1 -status[9] = 2 -status[10] = 3 -status[11] = 3 -~~~ - -These bits are concatenated: - -~~~ ascii-art - -byte 0 1 2 -bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 - +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ -values |1|1|0|0|1|0|0|1| |0|1|0|0|0|1|0|0| |1|1|1|1|1|0|0|1| - +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ - \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / -status 3 0 2 1 1 0 1 0 3 3 2 1 -index 3 2 1 0 7 6 5 4 11 10 9 8 - \___________/ \___________/ \___________/ - 0xC9 0x44 0xF9 - -~~~ - -Resulting in the byte array and compressed/base64url-encoded Status List: - -~~~~~~~~~~ -{::include ./examples/status_list_encoding2_json} -~~~~~~~~~~ - # Security Considerations {#Security} The Status List as defined in [](#status-list) only exists in cryptographically secured containers which allows checking the integrity and origin without relying on other aspects like transport security (e.g., the web PKI). @@ -1322,6 +1322,387 @@ Torsten Lodderstedt for their valuable contributions, discussions and feedback to this specification. +# Test vectors for Status List encoding {#test-vectors} +{:unnumbered} + +All examples here are given in the form of JSON or CBOR payloads. The examples are encoded according to [](#status-list-json) for JSON and [](#status-list-cbor) for CBOR. The CBOR examples are displayed as hex values. + +All values that are not mentioned for the examples below can be assumed to be 0 (VALID). All examples are initialized with a size of 2^20 entries. + +## 1 bit Status List +{:unnumbered} + +The following example uses a 1 bit Status List (2 possible values): + +~~~~~~~~~~ +status[0]=1 +status[1993]=1 +status[25460]=1 +status[159495]=1 +status[495669]=1 +status[554353]=1 +status[645645]=1 +status[723232]=1 +status[854545]=1 +status[934534]=1 +status[1000345]=1 +~~~~~~~~~~ + +JSON encoding: + +~~~~~~~~~~ +{::include ./examples/status_list_encoding1_long_json} +~~~~~~~~~~ + +CBOR encoding: + +~~~~~~~~~~ +{::include ./examples/status_list_encoding1_long_cbor} +~~~~~~~~~~ + +## 2 bit Status List +{:unnumbered} + +The following example uses a 2 bit Status List (4 possible values): + +~~~~~~~~~~ +status[0]=1 +status[1993]=2 +status[25460]=1 +status[159495]=3 +status[495669]=1 +status[554353]=1 +status[645645]=2 +status[723232]=1 +status[854545]=1 +status[934534]=2 +status[1000345]=3 +~~~~~~~~~~ + +JSON encoding: + +~~~~~~~~~~ +{::include ./examples/status_list_encoding2_long_json} +~~~~~~~~~~ + +CBOR encoding: + +~~~~~~~~~~ +{::include ./examples/status_list_encoding2_long_cbor} +~~~~~~~~~~ + +## 4 bit Status List +{:unnumbered} + +The following example uses a 4 bit Status List (16 possible values): + +~~~~~~~~~~ +status[0]=1 +status[1993]=2 +status[35460]=3 +status[459495]=4 +status[595669]=5 +status[754353]=6 +status[845645]=7 +status[923232]=8 +status[924445]=9 +status[934534]=10 +status[1004534]=11 +status[1000345]=12 +status[1030203]=13 +status[1030204]=14 +status[1030205]=15 +~~~~~~~~~~ + +JSON encoding: + +~~~~~~~~~~ +{::include ./examples/status_list_encoding4_json} +~~~~~~~~~~ + +CBOR encoding: + +~~~~~~~~~~ +{::include ./examples/status_list_encoding4_cbor} +~~~~~~~~~~ + +## 8 bit Status List +{:unnumbered} + +The following example uses a 8 bit Status List (256 possible values): + +~~~~~~~~~~ +status[233478] = 0 +status[52451] = 1 +status[576778] = 2 +status[513575] = 3 +status[468106] = 4 +status[292632] = 5 +status[214947] = 6 +status[182323] = 7 +status[884834] = 8 +status[66653] = 9 +status[62489] = 10 +status[196493] = 11 +status[458517] = 12 +status[487925] = 13 +status[55649] = 14 +status[416992] = 15 +status[879796] = 16 +status[462297] = 17 +status[942059] = 18 +status[583408] = 19 +status[13628] = 20 +status[334829] = 21 +status[886286] = 22 +status[713557] = 23 +status[582738] = 24 +status[326064] = 25 +status[451545] = 26 +status[705889] = 27 +status[214350] = 28 +status[194502] = 29 +status[796765] = 30 +status[202828] = 31 +status[752834] = 32 +status[721327] = 33 +status[554740] = 34 +status[91122] = 35 +status[963483] = 36 +status[261779] = 37 +status[793844] = 38 +status[165255] = 39 +status[614839] = 40 +status[758403] = 41 +status[403258] = 42 +status[145867] = 43 +status[96100] = 44 +status[477937] = 45 +status[606890] = 46 +status[167335] = 47 +status[488197] = 48 +status[211815] = 49 +status[797182] = 50 +status[582952] = 51 +status[950870] = 52 +status[765108] = 53 +status[341110] = 54 +status[776325] = 55 +status[745056] = 56 +status[439368] = 57 +status[559893] = 58 +status[149741] = 59 +status[358903] = 60 +status[513405] = 61 +status[342679] = 62 +status[969429] = 63 +status[795775] = 64 +status[566121] = 65 +status[460566] = 66 +status[680070] = 67 +status[117310] = 68 +status[480348] = 69 +status[67319] = 70 +status[661552] = 71 +status[841303] = 72 +status[561493] = 73 +status[138807] = 74 +status[442463] = 75 +status[659927] = 76 +status[445910] = 77 +status[1046963] = 78 +status[829700] = 79 +status[962282] = 80 +status[299623] = 81 +status[555493] = 82 +status[292826] = 83 +status[517215] = 84 +status[551009] = 85 +status[898490] = 86 +status[837603] = 87 +status[759161] = 88 +status[459948] = 89 +status[290102] = 90 +status[1034977] = 91 +status[190650] = 92 +status[98810] = 93 +status[229950] = 94 +status[320531] = 95 +status[335506] = 96 +status[885333] = 97 +status[133227] = 98 +status[806915] = 99 +status[800313] = 100 +status[981571] = 101 +status[527253] = 102 +status[24077] = 103 +status[240232] = 104 +status[559572] = 105 +status[713399] = 106 +status[233941] = 107 +status[615514] = 108 +status[911768] = 109 +status[331680] = 110 +status[951527] = 111 +status[6805] = 112 +status[552366] = 113 +status[374660] = 114 +status[223159] = 115 +status[625884] = 116 +status[417146] = 117 +status[320527] = 118 +status[784154] = 119 +status[338792] = 120 +status[1199] = 121 +status[679804] = 122 +status[1024680] = 123 +status[40845] = 124 +status[234603] = 125 +status[761225] = 126 +status[644903] = 127 +status[502167] = 128 +status[121477] = 129 +status[505144] = 130 +status[165165] = 131 +status[179628] = 132 +status[1019195] = 133 +status[145149] = 134 +status[263738] = 135 +status[269256] = 136 +status[996739] = 137 +status[346296] = 138 +status[555864] = 139 +status[887384] = 140 +status[444173] = 141 +status[421844] = 142 +status[653716] = 143 +status[836747] = 144 +status[783119] = 145 +status[918762] = 146 +status[946835] = 147 +status[253764] = 148 +status[519895] = 149 +status[471224] = 150 +status[134272] = 151 +status[709016] = 152 +status[44112] = 153 +status[482585] = 154 +status[461829] = 155 +status[15080] = 156 +status[148883] = 157 +status[123467] = 158 +status[480125] = 159 +status[141348] = 160 +status[65877] = 161 +status[692958] = 162 +status[148598] = 163 +status[499131] = 164 +status[584009] = 165 +status[1017987] = 166 +status[449287] = 167 +status[277478] = 168 +status[991262] = 169 +status[509602] = 170 +status[991896] = 171 +status[853666] = 172 +status[399318] = 173 +status[197815] = 174 +status[203278] = 175 +status[903979] = 176 +status[743015] = 177 +status[888308] = 178 +status[862143] = 179 +status[979421] = 180 +status[113605] = 181 +status[206397] = 182 +status[127113] = 183 +status[844358] = 184 +status[711569] = 185 +status[229153] = 186 +status[521470] = 187 +status[401793] = 188 +status[398896] = 189 +status[940810] = 190 +status[293983] = 191 +status[884749] = 192 +status[384802] = 193 +status[584151] = 194 +status[970201] = 195 +status[523882] = 196 +status[158093] = 197 +status[929312] = 198 +status[205329] = 199 +status[106091] = 200 +status[30949] = 201 +status[195586] = 202 +status[495723] = 203 +status[348779] = 204 +status[852312] = 205 +status[1018463] = 206 +status[1009481] = 207 +status[448260] = 208 +status[841042] = 209 +status[122967] = 210 +status[345269] = 211 +status[794764] = 212 +status[4520] = 213 +status[818773] = 214 +status[556171] = 215 +status[954221] = 216 +status[598210] = 217 +status[887110] = 218 +status[1020623] = 219 +status[324632] = 220 +status[398244] = 221 +status[622241] = 222 +status[456551] = 223 +status[122648] = 224 +status[127837] = 225 +status[657676] = 226 +status[119884] = 227 +status[105156] = 228 +status[999897] = 229 +status[330160] = 230 +status[119285] = 231 +status[168005] = 232 +status[389703] = 233 +status[143699] = 234 +status[142524] = 235 +status[493258] = 236 +status[846778] = 237 +status[251420] = 238 +status[516351] = 239 +status[83344] = 240 +status[171931] = 241 +status[879178] = 242 +status[663475] = 243 +status[546865] = 244 +status[428362] = 245 +status[658891] = 246 +status[500560] = 247 +status[557034] = 248 +status[830023] = 249 +status[274471] = 250 +status[629139] = 251 +status[958869] = 252 +status[663071] = 253 +status[152133] = 254 +status[19535] = 255 +~~~~~~~~~~ + +JSON encoding: + +~~~~~~~~~~ +{::include ./examples/status_list_encoding8_json} +~~~~~~~~~~ + +CBOR encoding: + +~~~~~~~~~~ +{::include ./examples/status_list_encoding8_cbor} +~~~~~~~~~~ + + # Document History {:numbered="false"} @@ -1335,6 +1716,7 @@ for their valuable contributions, discussions and feedback to this specification * changes as requested by IANA review * emphasize that security and privacy considerations only apply to Status List and no other status mechanisms * differentiate unlinkability between Issuer-RP and RP-RP +* add more test vectors for the status list encoding * add prior art -06 diff --git a/src/main.py b/src/main.py index cea5382..6bed4d5 100644 --- a/src/main.py +++ b/src/main.py @@ -1,15 +1,18 @@ -from status_list import StatusList -from status_token import StatusListToken -from referenced_token import CWT -from datetime import datetime, timedelta, timezone import os +import random +from datetime import datetime, timedelta, timezone + import util +from referenced_token import CWT +from status_list import StatusList +from status_token import StatusListToken key = util.EXAMPLE_KEY iat = datetime.fromtimestamp(1686920170, timezone.utc) exp = iat + timedelta(days=7000) ttl = timedelta(hours=12) folder = "./examples/" +long_size = 2**20 def exampleStatusList1Bit() -> StatusList: @@ -50,6 +53,68 @@ def exampleStatusList2Bit() -> StatusList: return status_list +def exampleStatusList1BitLong() -> StatusList: + status_list = StatusList(long_size, 1) + status_list.set(0, 1) + status_list.set(1993, 1) + status_list.set(25460, 1) + status_list.set(159495, 1) + status_list.set(495669, 1) + status_list.set(554353, 1) + status_list.set(645645, 1) + status_list.set(723232, 1) + status_list.set(854545, 1) + status_list.set(934534, 1) + status_list.set(1000345, 1) + return status_list + + +def exampleStatusList2BitLong() -> StatusList: + status_list = StatusList(long_size, 2) + status_list.set(0, 1) + status_list.set(1993, 2) + status_list.set(25460, 1) + status_list.set(159495, 3) + status_list.set(495669, 1) + status_list.set(554353, 1) + status_list.set(645645, 2) + status_list.set(723232, 1) + status_list.set(854545, 1) + status_list.set(934534, 2) + status_list.set(1000345, 3) + return status_list + + +def exampleStatusList4Bit() -> StatusList: + status_list = StatusList(long_size, 4) + status_list.set(0, 1) + status_list.set(1993, 2) + status_list.set(35460, 3) + status_list.set(459495, 4) + status_list.set(595669, 5) + status_list.set(754353, 6) + status_list.set(845645, 7) + status_list.set(923232, 8) + status_list.set(924445, 9) + status_list.set(934534, 10) + status_list.set(1004534, 11) + status_list.set(1000345, 12) + status_list.set(1030203, 13) + status_list.set(1030204, 14) + status_list.set(1030205, 15) + return status_list + + +def exampleStatusList8Bit() -> StatusList: + status_list = StatusList(long_size, 8) + random.seed(42) + for x in range(2**8): + y = random.randint(0, long_size - 1) + # print("status[{}] = {}".format(y,x)) + status_list.set(y, x) + return status_list + + def statusListEncoding1Bit(): status_list = exampleStatusList1Bit() encoded = status_list.encodeAsJSON() @@ -59,6 +124,15 @@ def statusListEncoding1Bit(): util.outputFile(folder + "status_list_encoding_json", text) +def statusListEncoding1BitLong(): + status_list = exampleStatusList1BitLong() + encoded = status_list.encodeAsJSON() + text = "{}".format( + util.printLongJsonObject(encoded), + ) + util.outputFile(folder + "status_list_encoding1_long_json", text) + + def statusListEncoding1BitCBOR(): status_list = exampleStatusList1Bit() encoded = status_list.encodeAsCBORRaw() @@ -71,6 +145,14 @@ def statusListEncoding1BitCBOR(): util.outputFile(folder + "status_list_encoding_cbor_diag", diag) +def statusListEncoding1BitLongCBOR(): + status_list = exampleStatusList1BitLong() + encoded = status_list.encodeAsCBORRaw() + hex_encoded = encoded.hex() + text = "{}".format(util.printText(hex_encoded)) + util.outputFile(folder + "status_list_encoding1_long_cbor", text) + + def statusListEncoding2Bit(): status_list = exampleStatusList2Bit() encoded = status_list.encodeAsJSON() @@ -83,6 +165,57 @@ def statusListEncoding2Bit(): util.outputFile(folder + "status_list_encoding2_json", text) +def statusListEncoding2BitLong(): + status_list = exampleStatusList2BitLong() + encoded = status_list.encodeAsJSON() + text = "{}".format( + util.printLongJsonObject(encoded), + ) + util.outputFile(folder + "status_list_encoding2_long_json", text) + + +def statusListEncoding2BitLongCBOR(): + status_list = exampleStatusList2BitLong() + encoded = status_list.encodeAsCBORRaw() + hex_encoded = encoded.hex() + text = "{}".format(util.printText(hex_encoded)) + util.outputFile(folder + "status_list_encoding2_long_cbor", text) + + +def statusListEncoding4Bit(): + status_list = exampleStatusList4Bit() + encoded = status_list.encodeAsJSON() + text = "{}".format( + util.printLongJsonObject(encoded), + ) + util.outputFile(folder + "status_list_encoding4_json", text) + + +def statusListEncoding4BitCBOR(): + status_list = exampleStatusList4Bit() + encoded = status_list.encodeAsCBORRaw() + hex_encoded = encoded.hex() + text = "{}".format(util.printText(hex_encoded)) + util.outputFile(folder + "status_list_encoding4_cbor", text) + + +def statusListEncoding8Bit(): + status_list = exampleStatusList8Bit() + encoded = status_list.encodeAsJSON() + text = "{}".format( + util.printLongJsonObject(encoded), + ) + util.outputFile(folder + "status_list_encoding8_json", text) + + +def statusListEncoding8BitCBOR(): + status_list = exampleStatusList8Bit() + encoded = status_list.encodeAsCBORRaw() + hex_encoded = encoded.hex() + text = "{}".format(util.printText(hex_encoded)) + util.outputFile(folder + "status_list_encoding8_cbor", text) + + def statusListEncoding2BitCBOR(): status_list = exampleStatusList2Bit() encoded = status_list.encodeAsCBORRaw() @@ -160,10 +293,19 @@ def referencedTokenCWT(): if not os.path.exists(folder): os.makedirs(folder) statusListEncoding1Bit() + statusListEncoding1BitLong() + statusListEncoding1BitLongCBOR() statusListEncoding2Bit() + statusListEncoding2BitLong() + statusListEncoding2BitLongCBOR() + statusListEncoding4Bit() + statusListEncoding4BitCBOR() + statusListEncoding8Bit() + statusListEncoding8BitCBOR() statusListJWT() statusListJWTRaw() statusListEncoding1BitCBOR() statusListEncoding2BitCBOR() statusListCWT() referencedTokenCWT() + referencedTokenCWT() diff --git a/src/referenced_token.py b/src/referenced_token.py index 2d9cbf8..8f1adf8 100644 --- a/src/referenced_token.py +++ b/src/referenced_token.py @@ -1,6 +1,7 @@ -from cbor2 import dumps -from cwt import COSE, COSEHeaders, COSEKey, CWTClaims, COSEAlgs from datetime import datetime + +from cbor2 import dumps +from cwt import COSE, COSEAlgs, COSEHeaders, COSEKey, CWTClaims from jwcrypto import jwk @@ -42,3 +43,4 @@ def CWT( ) return encoded + return encoded diff --git a/src/status_list.py b/src/status_list.py index 7c3e48f..08c4bfc 100644 --- a/src/status_list.py +++ b/src/status_list.py @@ -1,6 +1,7 @@ +import zlib from base64 import urlsafe_b64decode, urlsafe_b64encode from typing import Dict -import zlib + from cbor2 import dumps, loads @@ -73,3 +74,4 @@ def __str__(self): for x in range(0, self.size): val = val + format(self.get(x), "x") return val + return val diff --git a/src/util.py b/src/util.py index c3e25cb..57c64e6 100644 --- a/src/util.py +++ b/src/util.py @@ -30,14 +30,24 @@ def formatToken(input: str, key: jwk.JWK) -> str: def printJson(input: str) -> str: - text = json.dumps(json.loads(input), sort_keys=True, indent=2, ensure_ascii=False) - return text + return json.dumps(json.loads(input), sort_keys=True, indent=2, ensure_ascii=False) def printObject(input: Dict) -> str: return printJson(json.dumps(input)) +def printLongJsonObject(input: Dict) -> str: + text = printJson(json.dumps(input)) + return fill( + text, + width=MAX_LENGTH, + break_on_hyphens=False, + replace_whitespace=False, + subsequent_indent=" ", + ) + + def printText(input: str) -> str: return fill(input, width=MAX_LENGTH, break_on_hyphens=False)