From d892e2e005a1f48a6935e7cfff12412ac6deb695 Mon Sep 17 00:00:00 2001 From: Shubham Verekar Date: Tue, 25 Mar 2025 16:36:56 +0530 Subject: [PATCH] Updated cwt module with signing functionality --- delivery/common/cwt/README.md | 7 +- .../common/cwt/examples/cwt-es256/bundle.json | 2 +- .../common/cwt/examples/cwt-es256/cbor-x.js | 114 +- delivery/common/cwt/examples/cwt-es256/cwt.js | 266 +++- .../common/cwt/examples/cwt-es256/main.js | 100 +- .../common/cwt/examples/cwt-hs256/bundle.json | 2 +- .../common/cwt/examples/cwt-hs256/cbor-x.js | 122 +- delivery/common/cwt/examples/cwt-hs256/cwt.js | 266 +++- .../common/cwt/examples/cwt-hs256/main.js | 70 +- .../common/cwt/examples/cwt-ps256/README.md | 2 + .../common/cwt/examples/cwt-ps256/bundle.json | 4 + .../common/cwt/examples/cwt-ps256/cbor-x.js | 1247 +++++++++++++++++ delivery/common/cwt/examples/cwt-ps256/cwt.js | 360 +++++ .../common/cwt/examples/cwt-ps256/main.js | 121 ++ .../cwt/examples/typescript/src/cwt/cbor-x.js | 122 +- .../cwt/examples/typescript/src/cwt/cwt.d.ts | 100 +- .../cwt/examples/typescript/src/cwt/cwt.js | 266 +++- .../cwt/examples/typescript/src/main.ts | 120 +- delivery/common/cwt/lib/README.md | 22 +- delivery/common/cwt/lib/cbor-x.js | 114 +- delivery/common/cwt/lib/cwt.d.ts | 100 +- delivery/common/cwt/lib/cwt.js | 266 +++- 22 files changed, 3257 insertions(+), 536 deletions(-) create mode 100644 delivery/common/cwt/examples/cwt-ps256/README.md create mode 100644 delivery/common/cwt/examples/cwt-ps256/bundle.json create mode 100644 delivery/common/cwt/examples/cwt-ps256/cbor-x.js create mode 100644 delivery/common/cwt/examples/cwt-ps256/cwt.js create mode 100644 delivery/common/cwt/examples/cwt-ps256/main.js diff --git a/delivery/common/cwt/README.md b/delivery/common/cwt/README.md index 40516968..96dab933 100644 --- a/delivery/common/cwt/README.md +++ b/delivery/common/cwt/README.md @@ -1,9 +1,9 @@ # CWT Module -The CWT module can be used to perform operations related to CWT tokens such as CWT token generation and validation. The module considers CWT tokens defined as per this [spec](https://www.rfc-editor.org/rfc/rfc8392.html). It exports implementations of CWTValidator class that contains API's to validate CWT tokens. +The CWT module can be used to perform operations related to CWT tokens such as CWT token generation and validation. The module considers CWT tokens defined as per this [spec](https://www.rfc-editor.org/rfc/rfc8392.html). It exports implementations of CWTValidator, CWTGenerator class that contains API's to validate/sign CWT tokens respectively. ## Limitations -- Currently the module only support API's for verification of CWT tokens that are generated using HS256, ES256 algorithm only. +- Currently the module only support API's for signature and verification of CWT tokens that are generated using HS256, ES256, PS256 algorithm only. - Currently the module only support MAC0 and Sign1 COSE message structure. Refer this [page](https://datatracker.ietf.org/doc/rfc8152/) for more details on CBOR Object Signing and Encryption (COSE). - As of now, EW do not support KMI to manage verification keys, hence these keys are fetched from property manager user defined variable which might not be a secure way. More details on user defined variables can be found [here](https://techdocs.akamai.com/property-mgr/docs/user-defined-vars) @@ -19,6 +19,3 @@ For more information on CWT Module, please refer to the following resources: ## Reporting Issues If you experience any problems, please raise a Github issue or create a pull request with fixes, suggestions, or code contributions. - -### Todo -- [ ] Complete E2E tests for ES256 diff --git a/delivery/common/cwt/examples/cwt-es256/bundle.json b/delivery/common/cwt/examples/cwt-es256/bundle.json index 2c527297..b6d2e882 100644 --- a/delivery/common/cwt/examples/cwt-es256/bundle.json +++ b/delivery/common/cwt/examples/cwt-es256/bundle.json @@ -1,4 +1,4 @@ { "edgeworker-version": "0.1", - "description" : "CWT validator example with ES256" + "description" : "CWT generator and validator example with ES256" } diff --git a/delivery/common/cwt/examples/cwt-es256/cbor-x.js b/delivery/common/cwt/examples/cwt-es256/cbor-x.js index 6e40b011..05f1b05e 100644 --- a/delivery/common/cwt/examples/cwt-es256/cbor-x.js +++ b/delivery/common/cwt/examples/cwt-es256/cbor-x.js @@ -11,7 +11,13 @@ const RECORD_DEFINITIONS_ID = 57342, RECORD_INLINE_ID = 57343, BUNDLED_STRINGS_I let currentStructures, srcString, bundledStrings$1, referenceMap, packedValues, dataView, restoreMapsAsObject, currentDecoder = {}, srcStringStart = 0, srcStringEnd = 0, currentExtensions = [], currentExtensionRanges = [], defaultOptions = { useRecords: !1, mapsAsObjects: !0 -}, sequentialMode = !1; +}, sequentialMode = !1, inlineObjectReadThreshold = 2; + +try { + new Function(""); +} catch (error) { + inlineObjectReadThreshold = 1 / 0; +} class Decoder { constructor(options) { @@ -201,9 +207,7 @@ function read() { return ~token; case 2: - return function(length) { - return currentDecoder.copyBuffers ? Uint8Array.prototype.slice.call(src, position$1, position$1 += length) : src.subarray(position$1, position$1 += length); - }(token); + return length = token, currentDecoder.copyBuffers ? Uint8Array.prototype.slice.call(src, position$1, position$1 += length) : src.subarray(position$1, position$1 += length); case 3: if (srcStringEnd >= position$1) return srcString.slice(position$1 - srcStringStart, (position$1 += token) - srcStringStart); @@ -245,10 +249,20 @@ function read() { if (structure) return structure.read || (structure.read = createStructureReader(structure)), structure.read(); if (token < 65536) { - if (token == RECORD_INLINE_ID) return recordDefinition(read()); + if (token == RECORD_INLINE_ID) { + let length = readJustLength(), id = read(), structure = read(); + recordDefinition(id, structure); + let object = {}; + if (currentDecoder.keyMap) for (let i = 2; i < length; i++) { + object[safeKey(currentDecoder.decodeKey(structure[i - 2]))] = read(); + } else for (let i = 2; i < length; i++) { + object[safeKey(structure[i - 2])] = read(); + } + return object; + } if (token == RECORD_DEFINITIONS_ID) { let length = readJustLength(), id = read(); - for (let i = 2; i < length; i++) recordDefinition([ id++, read() ]); + for (let i = 2; i < length; i++) recordDefinition(id++, read()); return read(); } if (token == BUNDLED_STRINGS_ID) return function() { @@ -305,6 +319,7 @@ function read() { } throw new Error("Unknown CBOR token " + token); } + var length; } const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/; @@ -333,7 +348,7 @@ function createStructureReader(structure) { if (compiledReader.propertyCount === length) return compiledReader(read); compiledReader = compiledReader.next; } - if (this.slowReads++ >= 3) { + if (this.slowReads++ >= inlineObjectReadThreshold) { let array = this.length == length ? this : this.slice(0, length); return compiledReader = currentDecoder.keyMap ? new Function("r", "return {" + array.map((k => currentDecoder.decodeKey(k))).map((k => validName.test(k) ? safeKey(k) + ":r()" : "[" + JSON.stringify(k) + "]:r()")).join(",") + "}") : new Function("r", "return {" + array.map((key => validName.test(key) ? safeKey(key) + ":r()" : "[" + JSON.stringify(key) + "]:r()")).join(",") + "}"), this.compiledReader && (compiledReader.next = this.compiledReader), compiledReader.propertyCount = length, @@ -346,7 +361,9 @@ function createStructureReader(structure) { } function safeKey(key) { - return "__proto__" === key ? "__proto_" : key; + if ("string" == typeof key) return "__proto__" === key ? "__proto_" : key; + if ("object" != typeof key) return key.toString(); + throw new Error("Invalid property name type " + typeof key); } let readFixedString = readStringJS, isNativeAccelerationEnabled = !1; @@ -467,20 +484,21 @@ currentExtensions[2] = buffer => { }, currentExtensions[3] = buffer => BigInt(-1) - currentExtensions[2](buffer), currentExtensions[4] = fraction => +(fraction[1] + "e" + fraction[0]), currentExtensions[5] = fraction => fraction[1] * Math.exp(fraction[0] * Math.log(2)); -const recordDefinition = definition => { - let id = definition[0] - 57344, structure = definition[1], existingStructure = currentStructures[id]; +const recordDefinition = (id, structure) => { + let existingStructure = currentStructures[id -= 57344]; existingStructure && existingStructure.isShared && ((currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure), currentStructures[id] = structure, structure.read = createStructureReader(structure); +}; + +currentExtensions[105] = data => { + let length = data.length, structure = data[1]; + recordDefinition(data[0], structure); let object = {}; - if (currentDecoder.keyMap) for (let i = 2, l = definition.length; i < l; i++) { - object[safeKey(currentDecoder.decodeKey(structure[i - 2]))] = definition[i]; - } else for (let i = 2, l = definition.length; i < l; i++) { - object[safeKey(structure[i - 2])] = definition[i]; + for (let i = 2; i < length; i++) { + object[safeKey(structure[i - 2])] = data[i]; } return object; -}; - -currentExtensions[105] = recordDefinition, currentExtensions[14] = value => bundledStrings$1 ? bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 += value) : new Tag(value, 14), +}, currentExtensions[14] = value => bundledStrings$1 ? bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 += value) : new Tag(value, 14), currentExtensions[15] = value => bundledStrings$1 ? bundledStrings$1[1].slice(bundledStrings$1.position1, bundledStrings$1.position1 += value) : new Tag(value, 15); let glbl = { @@ -491,8 +509,15 @@ let glbl = { currentExtensions[27] = data => (glbl[data[0]] || Error)(data[1], data[2]); const packedTable = read => { - if (132 != src[position$1++]) throw new Error("Packed values structure must be followed by a 4 element array"); + if (132 != src[position$1++]) { + let error = new Error("Packed values structure must be followed by a 4 element array"); + throw src.length < position$1 && (error.incomplete = !0), error; + } let newPackedValues = read(); + if (!newPackedValues || !newPackedValues.length) { + let error = new Error("Packed values structure must be followed by a 4 element array"); + throw error.incomplete = !0, error; + } return packedValues = packedValues ? newPackedValues.concat(packedValues.slice(newPackedValues.length)) : newPackedValues, packedValues.prefixes = read(), packedValues.suffixes = read(), read(); }; @@ -515,7 +540,8 @@ packedTable.handlesRead = !0, currentExtensions[51] = packedTable, currentExtens loadShared(); } if ("number" == typeof data) return packedValues[16 + (data >= 0 ? 2 * data : -2 * data - 1)]; - throw new Error("No support for non-integer packed references yet"); + let error = new Error("No support for non-integer packed references yet"); + throw void 0 === data && (error.incomplete = !0), error; }, currentExtensions[28] = read => { referenceMap || (referenceMap = new Map, referenceMap.id = 0); let target, id = referenceMap.id++; @@ -548,15 +574,14 @@ const isLittleEndianMachine$1 = 1 == new Uint8Array(new Uint16Array([ 1 ]).buffe for (let i = 0; i < typedArrays.length; i++) registerTypedArray(typedArrays[i], typedArrayTags[i]); function registerTypedArray(TypedArray, tag) { - let dvMethod = "get" + TypedArray.name.slice(0, -5); - "function" != typeof TypedArray && (TypedArray = null); - let bytesPerElement = TypedArray.BYTES_PER_ELEMENT; + let bytesPerElement, dvMethod = "get" + TypedArray.name.slice(0, -5); + "function" == typeof TypedArray ? bytesPerElement = TypedArray.BYTES_PER_ELEMENT : TypedArray = null; for (let littleEndian = 0; littleEndian < 2; littleEndian++) { if (!littleEndian && 1 == bytesPerElement) continue; let sizeShift = 2 == bytesPerElement ? 1 : 4 == bytesPerElement ? 2 : 3; currentExtensions[littleEndian ? tag : tag - 4] = 1 == bytesPerElement || littleEndian == isLittleEndianMachine$1 ? buffer => { if (!TypedArray) throw new Error("Could not find typed array for code " + tag); - return new TypedArray(Uint8Array.prototype.slice.call(buffer, 0).buffer); + return currentDecoder.copyBuffers || 1 !== bytesPerElement && (2 !== bytesPerElement || 1 & buffer.byteOffset) && (4 !== bytesPerElement || 3 & buffer.byteOffset) && (8 !== bytesPerElement || 7 & buffer.byteOffset) ? new TypedArray(Uint8Array.prototype.slice.call(buffer, 0).buffer) : new TypedArray(buffer.buffer, buffer.byteOffset, buffer.byteLength); } : buffer => { if (!TypedArray) throw new Error("Could not find typed array for code " + tag); let dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength), elements = buffer.length >> sizeShift, ta = new TypedArray(elements), method = dv[dvMethod]; @@ -631,7 +656,7 @@ try { textEncoder = new TextEncoder; } catch (error) {} -const hasNodeBuffer = "undefined" != typeof Buffer, ByteArrayAllocate = hasNodeBuffer ? Buffer.allocUnsafeSlow : Uint8Array, ByteArray = hasNodeBuffer ? Buffer : Uint8Array, BlobConstructor = "undefined" == typeof Blob ? {} : Blob, MAX_BUFFER_SIZE = hasNodeBuffer ? 4294967296 : 2144337920; +const Buffer$1 = "object" == typeof globalThis && globalThis.Buffer, hasNodeBuffer = void 0 !== Buffer$1, ByteArrayAllocate = hasNodeBuffer ? Buffer$1.allocUnsafeSlow : Uint8Array, ByteArray = hasNodeBuffer ? Buffer$1 : Uint8Array, MAX_BUFFER_SIZE = hasNodeBuffer ? 4294967296 : 2144337920; let throwOnIterable, target, targetView, safeEnd, position = 0, bundledStrings = null; @@ -804,13 +829,7 @@ class Encoder extends Decoder { target[position++] = 121, target[position++] = length >> 8, target[position++] = 255 & length) : (headerSize < 5 && target.copyWithin(position + 5, position + 3, position + 3 + length), target[position++] = 122, targetView.setUint32(position, length), position += 4), position += length; - } else if ("number" === type) if (value >>> 0 === value) value < 24 ? target[position++] = value : value < 256 ? (target[position++] = 24, - target[position++] = value) : value < 65536 ? (target[position++] = 25, target[position++] = value >> 8, - target[position++] = 255 & value) : (target[position++] = 26, targetView.setUint32(position, value), - position += 4); else if (value >> 0 === value) value >= -24 ? target[position++] = 31 - value : value >= -256 ? (target[position++] = 56, - target[position++] = ~value) : value >= -65536 ? (target[position++] = 57, targetView.setUint16(position, ~value), - position += 2) : (target[position++] = 58, targetView.setUint32(position, ~value), - position += 4); else { + } else if ("number" === type) if (this.alwaysUseFloat || value >>> 0 !== value) if (this.alwaysUseFloat || value >> 0 !== value) { let useFloat32; if ((useFloat32 = this.useFloat32) > 0 && value < 4294967296 && value >= -2147483648) { let xShifted; @@ -818,7 +837,13 @@ class Encoder extends Decoder { position--; } target[position++] = 251, targetView.setFloat64(position, value), position += 8; - } else if ("object" === type) if (value) { + } else value >= -24 ? target[position++] = 31 - value : value >= -256 ? (target[position++] = 56, + target[position++] = ~value) : value >= -65536 ? (target[position++] = 57, targetView.setUint16(position, ~value), + position += 2) : (target[position++] = 58, targetView.setUint32(position, ~value), + position += 4); else value < 24 ? target[position++] = value : value < 256 ? (target[position++] = 24, + target[position++] = value) : value < 65536 ? (target[position++] = 25, target[position++] = value >> 8, + target[position++] = 255 & value) : (target[position++] = 26, targetView.setUint32(position, value), + position += 4); else if ("object" === type) if (value) { if (referenceMap) { let referee = referenceMap.get(value); if (referee) { @@ -862,10 +887,14 @@ class Encoder extends Decoder { for (let entry of value) encode(entry); return void (target[position++] = 255); } - if (value[Symbol.asyncIterator] || constructor === BlobConstructor) { + if (value[Symbol.asyncIterator] || isBlob(value)) { let error = new Error("Iterable/blob should be serialized as iterator"); throw error.iteratorNotHandled = !0, error; } + if (this.useToJSON && value.toJSON) { + const json = value.toJSON(); + if (json !== value) return encode(json); + } writeObject(value, !value.hasOwnProperty); } } else target[position++] = 246; else if ("boolean" === type) target[position++] = value ? 245 : 244; else if ("bigint" === type) { @@ -884,7 +913,7 @@ class Encoder extends Decoder { if (length < 24 ? target[position++] = 160 | length : length < 256 ? (target[position++] = 184, target[position++] = length) : length < 65536 ? (target[position++] = 185, target[position++] = length >> 8, target[position++] = 255 & length) : (target[position++] = 186, targetView.setUint32(position, length), - position += 4), encoder.keyMap) for (let i = 0; i < length; i++) encode(encodeKey(keys[i])), + position += 4), encoder.keyMap) for (let i = 0; i < length; i++) encode(encoder.encodeKey(keys[i])), encode(vals[i]); else for (let i = 0; i < length; i++) encode(keys[i]), encode(vals[i]); } : (object, safePrototype) => { target[position++] = 185; @@ -952,7 +981,8 @@ class Encoder extends Decoder { useRecords || encode(key), value && "object" == typeof value ? iterateProperties[key] ? yield* encodeObjectAsIterable(value, iterateProperties[key]) : yield* tryEncode(value, iterateProperties, key) : encode(value); } } else if (constructor === Array) { - writeArrayHeader(object.length); + let length = object.length; + writeArrayHeader(length); for (let i = 0; i < length; i++) { let value = object[i]; value && ("object" == typeof value || position - start > chunkThreshold) ? iterateProperties.element ? yield* encodeObjectAsIterable(value, iterateProperties.element) : yield* tryEncode(value, iterateProperties, "element") : encode(value); @@ -961,7 +991,7 @@ class Encoder extends Decoder { target[position++] = 159; for (let value of object) value && ("object" == typeof value || position - start > chunkThreshold) ? iterateProperties.element ? yield* encodeObjectAsIterable(value, iterateProperties.element) : yield* tryEncode(value, iterateProperties, "element") : encode(value); target[position++] = 255; - } else constructor === BlobConstructor ? (writeEntityLength(object.size, 64), yield target.subarray(start, position), + } else isBlob(object) ? (writeEntityLength(object.size, 64), yield target.subarray(start, position), yield object, restartEncoding()) : object[Symbol.asyncIterator] ? (target[position++] = 159, yield target.subarray(start, position), yield object, restartEncoding(), target[position++] = 255) : encode(object); finalIterable && position > start ? yield target.subarray(start, position) : position - start > chunkThreshold && (yield target.subarray(start, position), @@ -987,7 +1017,7 @@ class Encoder extends Decoder { async function* encodeObjectAsAsyncIterable(value, iterateProperties) { for (let encodedValue of encodeObjectAsIterable(value, iterateProperties, !0)) { let constructor = encodedValue.constructor; - if (constructor === ByteArray || constructor === Uint8Array) yield encodedValue; else if (constructor === BlobConstructor) { + if (constructor === ByteArray || constructor === Uint8Array) yield encodedValue; else if (isBlob(encodedValue)) { let next, reader = encodedValue.stream().getReader(); for (;!(next = await reader.read()).done; ) yield next.value; } else if (encodedValue[Symbol.asyncIterator]) for await (let asyncValue of encodedValue) restartEncoding(), @@ -1038,6 +1068,14 @@ function writeArrayHeader(length) { position += 4); } +const BlobConstructor = "undefined" == typeof Blob ? function() {} : Blob; + +function isBlob(object) { + if (object instanceof BlobConstructor) return !0; + let tag = object[Symbol.toStringTag]; + return "Blob" === tag || "File" === tag; +} + function findRepetitiveStrings(value, packedValues) { switch (typeof value) { case "string": @@ -1075,7 +1113,7 @@ function typedArrayEncoder(tag, size) { tag, encode: function(typedArray, encode) { let length = typedArray.byteLength, offset = typedArray.byteOffset || 0, buffer = typedArray.buffer || typedArray; - encode(hasNodeBuffer ? Buffer.from(buffer, offset, length) : new Uint8Array(buffer, offset, length)); + encode(hasNodeBuffer ? Buffer$1.from(buffer, offset, length) : new Uint8Array(buffer, offset, length)); } }; } diff --git a/delivery/common/cwt/examples/cwt-es256/cwt.js b/delivery/common/cwt/examples/cwt-es256/cwt.js index caafead0..a207206b 100644 --- a/delivery/common/cwt/examples/cwt-es256/cwt.js +++ b/delivery/common/cwt/examples/cwt-es256/cwt.js @@ -1,21 +1,47 @@ -/** @preserve @version 1.1.1 */ +/** @preserve @version 1.2.0 */ import { crypto } from "crypto"; -import { Encoder, Decoder } from "./cbor-x.js"; +import { Encoder, Decoder, Tag } from "./cbor-x.js"; + +import { logger } from "log"; const COSE_Mac0 = 17, COSE_Mac = 97, COSE_Sign = 98, COSE_Sign1 = 18, COSE_Encrypt0 = 16, COSE_Encrypt = 96, coseAlgTags = { - 5: "HMAC 256/256", - "-7": "ES256" -}, HeaderLabelToKey_alg = 1, HeaderLabelToKey_crit = 2, claimsLabelToKey_iss = 1, claimsLabelToKey_sub = 2, claimsLabelToKey_aud = 3, claimsLabelToKey_exp = 4, claimsLabelToKey_nbf = 5; + "-7": "ES256", + 5: "HS256", + "-37": "PS256" +}, HeaderLabels = { + alg: 1, + crit: 2, + kid: 4 +}, ClaimLabels = { + iss: 1, + sub: 2, + aud: 3, + exp: 4, + nbf: 5, + iat: 6, + cti: 7 +}, AlgorithmLabels = { + ES256: -7, + HS256: 5, + PS256: -37 +}; class Mac { - static async verifyHMAC(alg, message, signature, keys) { - if ("HMAC 256/256" === alg) { - let isSignVerified = !1; - for (const key of keys) if (isSignVerified = await crypto.subtle.verify({ - name: "HMAC" - }, key, signature, message), isSignVerified) return Promise.resolve(isSignVerified); - return Promise.resolve(isSignVerified); + static async verifyHMAC(alg, message, signature, key) { + if ("HS256" === alg) return await crypto.subtle.verify({ + name: "HMAC" + }, key, signature, message); + throw new Error(`Unsupported Algorithm, ${alg}`); + } + static async doSign(signaturePayload, key, alg) { + if ("HS256" === alg) { + return await crypto.subtle.sign({ + name: "HMAC", + hash: { + name: "SHA-256" + } + }, key, signaturePayload); } throw new Error(`Unsupported Algorithm, ${alg}`); } @@ -28,28 +54,68 @@ class CWTUtil { })).join(""); } static claimsTranslate(payload, labelsMap, translators) { - const result = {}; - for (const param in payload) { - const key = labelsMap[param] ? labelsMap[param] : param, theValue = translators && translators[key] ? translators[key](payload[param]) : payload[param]; - result[key] = theValue; + if (payload instanceof Map) { + const result = new Map; + for (const [k, v] of payload) { + const tK = labelsMap[k] ? labelsMap[k] : k, tV = translators && translators[tK] ? translators[tK](v) : v; + result.set(tK, tV); + } + return result; } - return result; + { + const result = new Map; + for (const param in payload) { + const key = labelsMap[param] ? labelsMap[param] : param, theValue = translators && translators[key] ? translators[key](payload[param]) : payload[param]; + result.set(key, theValue); + } + return result; + } + } + static isUint8ArrayEqual(arr1, arr2) { + return arr1 instanceof Uint8Array && (arr2 instanceof Uint8Array && (arr1.length === arr2.length && arr1.every(((value, index) => value === arr2[index])))); } } CWTUtil.EMPTY_BUFFER = new Uint8Array(0); class Sign { - static async verifySignature(alg, message, signature, keys) { - if ("ES256" === alg) { - let isSignVerified = !1; - for (const key of keys) if (isSignVerified = await crypto.subtle.verify({ + static async verifySignature(alg, message, signature, key) { + switch (alg) { + case "ES256": + return await crypto.subtle.verify({ name: "ECDSA", hash: "SHA-256" - }, key, signature, new Uint8Array(message)), isSignVerified) return Promise.resolve(isSignVerified); - return Promise.resolve(isSignVerified); + }, key, signature, new Uint8Array(message)); + + case "PS256": + return await crypto.subtle.verify({ + name: "RSA-PSS", + saltLength: 32 + }, key, signature, new Uint8Array(message)); + + default: + throw new Error(`Unsupported Algorithm, ${alg}`); + } + } + static async doSign(signaturePayload, key, alg) { + switch (alg) { + case "ES256": + return await crypto.subtle.sign({ + name: "ECDSA", + hash: { + name: "SHA-256" + } + }, key, signaturePayload); + + case "PS256": + return await crypto.subtle.sign({ + name: "RSA-PSS", + saltLength: 32 + }, key, signaturePayload); + + default: + throw new Error(`Unsupported Algorithm, ${alg}`); } - throw new Error(`Unsupported Algorithm, ${alg}`); } } @@ -61,10 +127,9 @@ class CWTValidator { constructor(cwtOptions) { this.cwtOptions = cwtOptions || {}, this.validateOptionTypes(); } - async validate(tokenBuf, keys, externalAAD) { + async validate(tokenBuf, verifiers) { if (!(tokenBuf instanceof Uint8Array)) throw new Error("Invalid token type, expected Uint8Array!"); - if (externalAAD && !(externalAAD instanceof Uint8Array)) throw new Error("Invalid externalAAD type, expected Uint8Array!"); - if (!Array.isArray(keys) || !keys.every((elem => void 0 !== elem.type || void 0 !== elem.extractable || null != elem.algorithm || null != elem.usages))) throw new Error("Invalid keys type, expected list of CryptoKey!"); + if (verifiers.find((elem => void 0 === elem.key || void 0 === elem.key.type || null == elem.key.algorithm || null == elem.key.usages || elem.externalAAD && !(elem.externalAAD instanceof Uint8Array) || elem.kid && !(elem.kid instanceof Uint8Array)))) throw new Error("Invalid verifiers, expected list of verifier with valid crypto key!"); let coseMessage = globalThis.cbordec.decode(tokenBuf), cwtType = this.cwtOptions.defaultCoseMsgType; if (this.cwtOptions.isCWTTagAdded) { if (61 !== coseMessage.tag) throw new Error("CWT malformed: expected CWT CBOR tag for the token!"); @@ -74,27 +139,31 @@ class CWTValidator { if (cwtType = coseMessage.tag, ![ COSE_Mac0, COSE_Mac, COSE_Sign, COSE_Sign1, COSE_Encrypt, COSE_Encrypt0 ].includes(cwtType)) throw new Error("CWT malformed: invalid COSE CBOR tag!"); coseMessage = coseMessage.value; } - externalAAD || (externalAAD = CWTUtil.EMPTY_BUFFER); - const cwtJSON = await this.verifyCoseMessage(coseMessage, cwtType, keys, this.cwtOptions.headerValidation, externalAAD); + const cwtJSON = await this.verifyCoseMessage(coseMessage, cwtType, verifiers, this.cwtOptions.headerValidation); return this.validateClaims(cwtJSON.payload), cwtJSON; } - async verifyCoseMessage(coseMessage, cwtType, keys, headerValidation, externalAAD) { + async verifyCoseMessage(coseMessage, cwtType, verifiers, headerValidation) { switch (cwtType) { case COSE_Mac0: { if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR MAC0Tag, expected array of length 4!"); const [p, u, payload, tag] = coseMessage; let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; - pH = pH.size ? pH : CWTUtil.EMPTY_BUFFER; const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; let alg; - if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabelToKey_alg); else { + if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabels.alg); else { if (uH === CWTUtil.EMPTY_BUFFER) throw new Error("CWT malformed: unable to find algo field from CWT token."); - alg = u.get(HeaderLabelToKey_alg); + alg = uH.get(HeaderLabels.alg); } - alg = coseAlgTags[alg.toString()]; - const MACstructure = [ "MAC0", p, externalAAD, payload ], toBeMACed = globalThis.cborenc.encode(MACstructure); - if (!await Mac.verifyHMAC(alg, toBeMACed, tag, keys)) throw new Error("CWT token signature verification failed!"); + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER; + alg = coseAlgTags[alg]; + let isSignVerified = !1; + for (const verifier of verifiers) { + const MACstructure = [ "MAC0", pP, verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, payload ], toBeVerified = globalThis.cborenc.encode(MACstructure); + if (isSignVerified = await Mac.verifyHMAC(alg, toBeVerified, tag, verifier.key), + isSignVerified) break; + } + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); const decodedPayload = globalThis.cbordec.decode(payload); return Promise.resolve({ header: { @@ -108,18 +177,57 @@ class CWTValidator { case COSE_Sign1: { if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR COSE_Sign1, expected array of length 4!"); - const [p, u, payload, signer] = coseMessage; + const [p, u, payload, signature] = coseMessage; let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; pH = pH.size ? pH : CWTUtil.EMPTY_BUFFER; const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; let alg; - if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabelToKey_alg); else { + if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabels.alg); else { if (uH === CWTUtil.EMPTY_BUFFER) throw new Error("CWT malformed: unable to find algo field from CWT token."); - alg = u.get(HeaderLabelToKey_alg); + alg = u.get(HeaderLabels.alg); + } + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER; + alg = coseAlgTags[alg]; + let isSignVerified = !1; + for (const verifier of verifiers) { + const SigStructure = [ "Signature1", pP, verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, payload ], toBeVerified = globalThis.cborenc.encode(SigStructure); + if (isSignVerified = await Sign.verifySignature(alg, toBeVerified, signature, verifier.key), + isSignVerified) break; } - alg = coseAlgTags[alg.toString()]; - const SigStructure = [ "Signature1", p, externalAAD, payload ], toBeVeried = globalThis.cborenc.encode(SigStructure); - if (!await Sign.verifySignature(alg, toBeVeried, signer, keys)) throw new Error("CWT token signature verification failed!"); + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); + const decodedPayload = globalThis.cbordec.decode(payload); + return Promise.resolve({ + header: { + p: pH, + u: uH + }, + payload: decodedPayload + }); + } + + case COSE_Sign: + { + if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR COSE_Sign, expected array of length 4!"); + const [p, u, payload, signatures] = coseMessage; + let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; + const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; + headerValidation && this.validateHeader(pH, !0); + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER, isSignVerified = !1; + for (const signature of signatures) { + let [signP, signU, sign] = signature; + const verifier = this.getVerifier(signU, verifiers); + if (verifier) { + const externalAAD = verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, signerPMap = signP.length ? globalThis.cborenc.decode(signP) : CWTUtil.EMPTY_BUFFER; + signP = signerPMap.size ? signP : CWTUtil.EMPTY_BUFFER; + const alg = signerPMap.get ? signerPMap.get(HeaderLabels.alg) : pH.get ? pH.get(HeaderLabels.alg) : void 0; + if (alg) { + const SigStructure = [ "Signature", pP, signP, externalAAD, payload ], toBeVerified = globalThis.cborenc.encode(SigStructure); + if (isSignVerified = await Sign.verifySignature(coseAlgTags[alg], toBeVerified, sign, verifier.key), + isSignVerified) break; + } else logger.error(`Unable to find alg in CWT token for kid = ${verifier.kid}, hence skipping the verifier!`); + } + } + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); const decodedPayload = globalThis.cbordec.decode(payload); return Promise.resolve({ header: { @@ -136,7 +244,7 @@ class CWTValidator { } validateHeader(headers, pheader) { if (headers.size && pheader) { - const h = headers, crit = h.get(HeaderLabelToKey_crit); + const h = headers, crit = h.get(HeaderLabels.crit); if (Array.isArray(crit)) { if (0 === crit.length) throw new Error("CWT Malformed: malformed protected header, crit array cannot be empty!"); for (const e of crit) if (void 0 === h.get(e)) throw new Error("CWT Malformed: malformed protected header, crit labels are not part of protected header"); @@ -145,29 +253,29 @@ class CWTValidator { } validateClaims(decodedPayload) { if (this.cwtOptions.issuer) { - const iss = decodedPayload.get(claimsLabelToKey_iss); + const iss = decodedPayload.get(ClaimLabels.iss); if (iss && this.cwtOptions.issuer !== iss) throw new Error(`CWT malformed: invalid iss, expected ${this.cwtOptions.issuer}`); } if (this.cwtOptions.subject) { - const sub = decodedPayload.get(claimsLabelToKey_sub); + const sub = decodedPayload.get(ClaimLabels.sub); if (sub && this.cwtOptions.subject !== sub) throw new Error(`CWT malformed: invalid sub, expected ${this.cwtOptions.subject}`); } if (this.cwtOptions.audience) { - const aud = decodedPayload.get(claimsLabelToKey_aud); + const aud = decodedPayload.get(ClaimLabels.aud); if (aud && (Array.isArray(aud) || "string" == typeof aud)) { if (!(Array.isArray(aud) ? aud : [ aud ]).includes(this.cwtOptions.audience)) throw new Error(`CWT malformed: invalid aud, expected ${this.cwtOptions.audience}`); } } const clockTimestamp = Math.floor(Date.now() / 1e3); if (!1 === this.cwtOptions.ignoreExpiration) { - const exp = decodedPayload.get(claimsLabelToKey_exp); + const exp = decodedPayload.get(ClaimLabels.exp); if (exp) { if ("number" != typeof exp) throw new Error("CWT malformed: exp must be number"); if (clockTimestamp > exp + (this.cwtOptions.clockTolerance || 0)) throw new Error("CWT token has been expired"); } } if (!1 === this.cwtOptions.ignoreNotBefore) { - const nbf = decodedPayload.get(claimsLabelToKey_nbf); + const nbf = decodedPayload.get(ClaimLabels.nbf); if (nbf) { if ("number" != typeof nbf) throw new Error("CWT malformed: nbf must be number"); if (nbf > clockTimestamp + (this.cwtOptions.clockTolerance || 0)) throw new Error("CWT is not active"); @@ -187,6 +295,66 @@ class CWTValidator { if (void 0 !== this.cwtOptions.clockTolerance && "number" != typeof this.cwtOptions.clockTolerance) throw new Error("Invalid cwtOptions: clockTolerance must be number"); this.cwtOptions.clockTolerance = 60; } + getVerifier(signU, verifiers) { + const kid = signU.get(HeaderLabels.kid); + for (const verifier of verifiers) if (CWTUtil.isUint8ArrayEqual(kid, verifier.kid)) return verifier; + return null; + } +} + +class CWTGenerator { + static async mac(claims, signer, contentHeader, recipients, options) { + if (!claims) throw new Error("Invalid claims type, cannot be null or undefined!"); + if (void 0 === signer.key || void 0 === signer.key.type || null == signer.key.algorithm || null == signer.key.usages || signer.externalAAD && !(signer.externalAAD instanceof Uint8Array)) throw new Error("Invalid signer, expected signer with valid crypto key!"); + if (!contentHeader) throw new Error("Invalid contentHeader type, cannot be null or undefined!"); + options = options || {}; + let pH = contentHeader && contentHeader.p ? contentHeader.p : new Map, uH = contentHeader && contentHeader.u ? contentHeader.u : new Map, protectedHeader = 0 === pH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(pH), alg = pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader!"); + let result, payload = globalThis.cborenc.encode(claims); + if (Array.isArray(recipients)) { + if (0 === recipients.length) throw new Error("No recipients found, there has to be atleast one recipients!"); + if (recipients.length > 0) throw new Error("Mac with recipients is not currently supported!"); + } else { + const MACstructure = [ "MAC0", protectedHeader, signer.externalAAD || CWTUtil.EMPTY_BUFFER, payload ]; + let signed = globalThis.cborenc.encode(MACstructure); + result = [ protectedHeader, uH, payload, await Mac.doSign(signed, signer.key, coseAlgTags[alg]) ], + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(result, COSE_Mac0) : result; + } + return result = options.isCWTTagAdded ? new Tag(result, 61) : result, globalThis.cborenc.encode(result); + } + static async sign(claims, signers, contentHeader, options) { + if (!claims) throw new Error("Invalid claims type, cannot be null or undefined!"); + if (!signers || Array.isArray(signers) && 0 == signers.length) throw new Error("Invalid signers, cannot be null or undefined, requies at atleast one signer!"); + if (Array.isArray(signers)) { + if (signers.find((elem => void 0 === elem.key || void 0 === elem.key.type || null == elem.key.algorithm || null == elem.key.usages || elem.externalAAD && !(elem.externalAAD instanceof Uint8Array)))) throw new Error("Invalid signers, expected list of signers with valid crypto key!"); + } else if (void 0 === signers.key || void 0 === signers.key.type || null == signers.key.algorithm || null == signers.key.usages || signers.externalAAD && !(signers.externalAAD instanceof Uint8Array)) throw new Error("Invalid signer, expected signer with valid crypto key!"); + options = options || {}; + let result, pH = contentHeader && contentHeader.p ? contentHeader.p : new Map, uH = contentHeader && contentHeader.u ? contentHeader.u : new Map, protectedHeader = 0 === pH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(pH), payload = globalThis.cborenc.encode(claims); + if (Array.isArray(signers)) { + let signatures = Array(); + for (const signer of signers) { + const externalAAD = signer.externalAAD || CWTUtil.EMPTY_BUFFER; + let signPH = signer.p ? signer.p : new Map, signUH = signer.u ? signer.u : new Map, alg = signPH.get(HeaderLabels.alg) || pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader or Signer object!"); + let signprotectedHeader = 0 === signPH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(signPH); + const SigStructure = [ "Signature", protectedHeader, signprotectedHeader, externalAAD, payload ]; + let signaturePayload = globalThis.cborenc.encode(SigStructure); + const sig = await Sign.doSign(signaturePayload, signer.key, coseAlgTags[alg]); + signatures.push([ signprotectedHeader, signUH, sig ]); + } + let signed = [ protectedHeader, uH, payload, signatures ]; + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(signed, COSE_Sign) : signed; + } else { + const externalAAD = signers.externalAAD || CWTUtil.EMPTY_BUFFER; + let alg = pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader!"); + const SigStructure = [ "Signature1", protectedHeader, externalAAD, payload ]; + let signaturePayload = globalThis.cborenc.encode(SigStructure); + let signed = [ protectedHeader, uH, payload, await Sign.doSign(signaturePayload, signers.key, coseAlgTags[alg]) ]; + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(signed, COSE_Sign1) : signed; + } + return result = options.isCWTTagAdded ? new Tag(result, 61) : result, globalThis.cborenc.encode(result); + } } -export { CWTUtil, CWTValidator }; +export { AlgorithmLabels, CWTGenerator, CWTUtil, CWTValidator, ClaimLabels, HeaderLabels }; diff --git a/delivery/common/cwt/examples/cwt-es256/main.js b/delivery/common/cwt/examples/cwt-es256/main.js index 83818cee..fca8366c 100644 --- a/delivery/common/cwt/examples/cwt-es256/main.js +++ b/delivery/common/cwt/examples/cwt-es256/main.js @@ -1,73 +1,93 @@ import { logger } from 'log'; -import { CWTUtil, CWTValidator} from './cwt.js'; +import { CWTUtil, CWTValidator, CWTGenerator, AlgorithmLabels, ClaimLabels, HeaderLabels} from './cwt.js'; import { crypto, pem2ab } from 'crypto'; import { base16 } from 'encoding'; //Integer keys mapping for CWT payload. This mapping is application specific, However keys from 1-7 are reserved const claimsLabelMap = { - 1: 'iss', 2: 'sub', 3: 'aud', 4: 'exp', 5: 'nbf', - 6: 'iat', - 7: 'cti', - 300: 'wmver', - 301: 'wmvnd', - 302: 'wmpatlen', - 303: 'wmsegduration', - 304: 'wmpattern', - 305: 'wmid', - 306: 'wmopid', - 307: 'wmkeyver' + 6: 'iat' }; - + //advanced options for cwt validator const cwtOptions = { //perform header validation - headerValidation: true, + headerValidation: false, //check token expiry - ignoreExpiration: false, + ignoreExpiration: true, //check token nbf - ignoreNotBefore: false + ignoreNotBefore: true }; +export const es256PrivKey1 = { + key_ops: ['sign'], + ext: false, + kty: 'EC', + x: 'D5fNFnQYFBOjWa1ndpQK3ZrzXuHD77oGDgPaMNbtZ7s', + y: 'Y4iS6G8atqp3x85xJOfCY997AVWHPy-dEgLk6CaNZ7w', + crv: 'P-256', + d: 'CyJoz5l2IG9cPEXvPATnU3BHrNS1Qx5-dZ4e_Z0H_3M' +}; + +const es256PubKey1 = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAED5fNFnQYFBOjWa1ndpQK3ZrzXuHD +77oGDgPaMNbtZ7tjiJLobxq2qnfHznEk58Jj33sBVYc/L50SAuToJo1nvA== +-----END PUBLIC KEY-----`; + const cwtValidator = new CWTValidator(cwtOptions); export async function onClientRequest (request) { try { - // Fetch hmac veification key from Propery Manager - const pubKey = request.getVariable('PMUSER_CWT_ES256_KEY'); - const sKey = await crypto.subtle.importKey( - 'spki', - pem2ab(pubKey), + const signingKey = await crypto.subtle.importKey( + 'jwk', + es256PrivKey1, { - name: 'HMAC', - hash: 'SHA-256' + name: 'ECDSA', + namedCurve: 'P-256' }, - false, - ['verify'] + !1, + ['sign'] ); - //Fetch the Authorization header from request - let cwt = request.getHeader('Authorization'); - if (cwt){ - cwt = cwt[0]; - //replace auth scheme before validating - cwt = cwt.replace('Bearer ',''); - //Assumption: CWT token as passed as hex encoded in authorization header. We decode the hex to get the binary - const tokenBuf = base16.decode(cwt,'Uint8Array'); - const cwtJSON = await cwtValidator.validate(tokenBuf,[sKey]); - const claims = CWTUtil.claimsTranslate(Object.fromEntries(new Map(cwtJSON.payload)),claimsLabelMap); - logger.log('cwtJSON %s: ',JSON.stringify(claims)); - request.respondWith(200, {}, JSON.stringify(claims)); + if (request.path == '/token' && request.method == 'POST') { + const claims = { iss: 'mde_dev@akamai.com', iat: Date.now(), sub: "subject@akamai.com", aud: ["cdn@akamai.com"], exp: Date.now() + 86400, nbf: Date.now() - 86400}; + const claimsSet = CWTUtil.claimsTranslate(claims, ClaimLabels); + const signer = { + key: signingKey + }; + const cwtToken = await CWTGenerator.sign(claimsSet, signer, { p: CWTUtil.claimsTranslate({ alg: AlgorithmLabels.ES256 }, HeaderLabels)}); + const cwtTokenHex = base16.encode(new Uint8Array(cwtToken)); + request.respondWith(200, {}, cwtTokenHex); } else { - //Return bad request of authorization header is not found - request.respondWith(400, {}, 'Authorization header is missing!'); + const verifyKey = await crypto.subtle.importKey( + 'spki', + pem2ab(es256PubKey1), + { name: "ECDSA", namedCurve: "P-256" }, + false, + ['verify'] + ); + //Fetch the Authorization header from request + let cwtToken = request.getHeader('Authorization'); + if (cwtToken){ + cwtToken = cwtToken[0]; + //replace auth scheme before validating + cwtToken = cwtToken.replace('Bearer ',''); + //Assumption: CWT token as passed as hex encoded in authorization header. We decode the hex to get the binary + const tokenBuf = base16.decode(cwtToken,'Uint8Array'); + const cwtJSON = await cwtValidator.validate(tokenBuf,[{key: verifyKey}]); + const claims = CWTUtil.claimsTranslate(cwtJSON.payload,claimsLabelMap); + request.respondWith(200, {}, JSON.stringify(Object.fromEntries(claims))); + } else { + //Return bad request of authorization header is not found + request.respondWith(400, {}, 'Authorization header is missing!'); + } } } catch (error) { logger.log(error); request.respondWith(400, {}, error.message); } -} +} \ No newline at end of file diff --git a/delivery/common/cwt/examples/cwt-hs256/bundle.json b/delivery/common/cwt/examples/cwt-hs256/bundle.json index 702bcf85..6241d276 100644 --- a/delivery/common/cwt/examples/cwt-hs256/bundle.json +++ b/delivery/common/cwt/examples/cwt-hs256/bundle.json @@ -1,4 +1,4 @@ { "edgeworker-version": "0.1", - "description" : "CWT validator example with HS256" + "description" : "CWT generator and validator example with HS256" } diff --git a/delivery/common/cwt/examples/cwt-hs256/cbor-x.js b/delivery/common/cwt/examples/cwt-hs256/cbor-x.js index dd12806f..05f1b05e 100644 --- a/delivery/common/cwt/examples/cwt-hs256/cbor-x.js +++ b/delivery/common/cwt/examples/cwt-hs256/cbor-x.js @@ -6,12 +6,18 @@ try { let position$1 = 0; -const STOP_CODE = {}; +const RECORD_DEFINITIONS_ID = 57342, RECORD_INLINE_ID = 57343, BUNDLED_STRINGS_ID = 57337, STOP_CODE = {}; let currentStructures, srcString, bundledStrings$1, referenceMap, packedValues, dataView, restoreMapsAsObject, currentDecoder = {}, srcStringStart = 0, srcStringEnd = 0, currentExtensions = [], currentExtensionRanges = [], defaultOptions = { useRecords: !1, mapsAsObjects: !0 -}, sequentialMode = !1; +}, sequentialMode = !1, inlineObjectReadThreshold = 2; + +try { + new Function(""); +} catch (error) { + inlineObjectReadThreshold = 1 / 0; +} class Decoder { constructor(options) { @@ -201,9 +207,7 @@ function read() { return ~token; case 2: - return function(length) { - return currentDecoder.copyBuffers ? Uint8Array.prototype.slice.call(src, position$1, position$1 += length) : src.subarray(position$1, position$1 += length); - }(token); + return length = token, currentDecoder.copyBuffers ? Uint8Array.prototype.slice.call(src, position$1, position$1 += length) : src.subarray(position$1, position$1 += length); case 3: if (srcStringEnd >= position$1) return srcString.slice(position$1 - srcStringStart, (position$1 += token) - srcStringStart); @@ -240,18 +244,28 @@ function read() { } case 6: - if (token >= 57337) { + if (token >= BUNDLED_STRINGS_ID) { let structure = currentStructures[8191 & token]; if (structure) return structure.read || (structure.read = createStructureReader(structure)), structure.read(); if (token < 65536) { - if (57343 == token) return recordDefinition(read()); - if (57342 == token) { + if (token == RECORD_INLINE_ID) { + let length = readJustLength(), id = read(), structure = read(); + recordDefinition(id, structure); + let object = {}; + if (currentDecoder.keyMap) for (let i = 2; i < length; i++) { + object[safeKey(currentDecoder.decodeKey(structure[i - 2]))] = read(); + } else for (let i = 2; i < length; i++) { + object[safeKey(structure[i - 2])] = read(); + } + return object; + } + if (token == RECORD_DEFINITIONS_ID) { let length = readJustLength(), id = read(); - for (let i = 2; i < length; i++) recordDefinition([ id++, read() ]); + for (let i = 2; i < length; i++) recordDefinition(id++, read()); return read(); } - if (57337 == token) return function() { + if (token == BUNDLED_STRINGS_ID) return function() { let length = readJustLength(), bundlePosition = position$1 + read(); for (let i = 2; i < length; i++) { let bundleLength = readJustLength(); @@ -305,6 +319,7 @@ function read() { } throw new Error("Unknown CBOR token " + token); } + var length; } const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/; @@ -333,7 +348,7 @@ function createStructureReader(structure) { if (compiledReader.propertyCount === length) return compiledReader(read); compiledReader = compiledReader.next; } - if (this.slowReads++ >= 3) { + if (this.slowReads++ >= inlineObjectReadThreshold) { let array = this.length == length ? this : this.slice(0, length); return compiledReader = currentDecoder.keyMap ? new Function("r", "return {" + array.map((k => currentDecoder.decodeKey(k))).map((k => validName.test(k) ? safeKey(k) + ":r()" : "[" + JSON.stringify(k) + "]:r()")).join(",") + "}") : new Function("r", "return {" + array.map((key => validName.test(key) ? safeKey(key) + ":r()" : "[" + JSON.stringify(key) + "]:r()")).join(",") + "}"), this.compiledReader && (compiledReader.next = this.compiledReader), compiledReader.propertyCount = length, @@ -346,7 +361,9 @@ function createStructureReader(structure) { } function safeKey(key) { - return "__proto__" === key ? "__proto_" : key; + if ("string" == typeof key) return "__proto__" === key ? "__proto_" : key; + if ("object" != typeof key) return key.toString(); + throw new Error("Invalid property name type " + typeof key); } let readFixedString = readStringJS, isNativeAccelerationEnabled = !1; @@ -467,20 +484,21 @@ currentExtensions[2] = buffer => { }, currentExtensions[3] = buffer => BigInt(-1) - currentExtensions[2](buffer), currentExtensions[4] = fraction => +(fraction[1] + "e" + fraction[0]), currentExtensions[5] = fraction => fraction[1] * Math.exp(fraction[0] * Math.log(2)); -const recordDefinition = definition => { - let id = definition[0] - 57344, structure = definition[1], existingStructure = currentStructures[id]; +const recordDefinition = (id, structure) => { + let existingStructure = currentStructures[id -= 57344]; existingStructure && existingStructure.isShared && ((currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure), currentStructures[id] = structure, structure.read = createStructureReader(structure); +}; + +currentExtensions[105] = data => { + let length = data.length, structure = data[1]; + recordDefinition(data[0], structure); let object = {}; - if (currentDecoder.keyMap) for (let i = 2, l = definition.length; i < l; i++) { - object[safeKey(currentDecoder.decodeKey(structure[i - 2]))] = definition[i]; - } else for (let i = 2, l = definition.length; i < l; i++) { - object[safeKey(structure[i - 2])] = definition[i]; + for (let i = 2; i < length; i++) { + object[safeKey(structure[i - 2])] = data[i]; } return object; -}; - -currentExtensions[105] = recordDefinition, currentExtensions[14] = value => bundledStrings$1 ? bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 += value) : new Tag(value, 14), +}, currentExtensions[14] = value => bundledStrings$1 ? bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 += value) : new Tag(value, 14), currentExtensions[15] = value => bundledStrings$1 ? bundledStrings$1[1].slice(bundledStrings$1.position1, bundledStrings$1.position1 += value) : new Tag(value, 15); let glbl = { @@ -491,8 +509,15 @@ let glbl = { currentExtensions[27] = data => (glbl[data[0]] || Error)(data[1], data[2]); const packedTable = read => { - if (132 != src[position$1++]) throw new Error("Packed values structure must be followed by a 4 element array"); + if (132 != src[position$1++]) { + let error = new Error("Packed values structure must be followed by a 4 element array"); + throw src.length < position$1 && (error.incomplete = !0), error; + } let newPackedValues = read(); + if (!newPackedValues || !newPackedValues.length) { + let error = new Error("Packed values structure must be followed by a 4 element array"); + throw error.incomplete = !0, error; + } return packedValues = packedValues ? newPackedValues.concat(packedValues.slice(newPackedValues.length)) : newPackedValues, packedValues.prefixes = read(), packedValues.suffixes = read(), read(); }; @@ -515,7 +540,8 @@ packedTable.handlesRead = !0, currentExtensions[51] = packedTable, currentExtens loadShared(); } if ("number" == typeof data) return packedValues[16 + (data >= 0 ? 2 * data : -2 * data - 1)]; - throw new Error("No support for non-integer packed references yet"); + let error = new Error("No support for non-integer packed references yet"); + throw void 0 === data && (error.incomplete = !0), error; }, currentExtensions[28] = read => { referenceMap || (referenceMap = new Map, referenceMap.id = 0); let target, id = referenceMap.id++; @@ -548,15 +574,14 @@ const isLittleEndianMachine$1 = 1 == new Uint8Array(new Uint16Array([ 1 ]).buffe for (let i = 0; i < typedArrays.length; i++) registerTypedArray(typedArrays[i], typedArrayTags[i]); function registerTypedArray(TypedArray, tag) { - let dvMethod = "get" + TypedArray.name.slice(0, -5); - "function" != typeof TypedArray && (TypedArray = null); - let bytesPerElement = TypedArray.BYTES_PER_ELEMENT; + let bytesPerElement, dvMethod = "get" + TypedArray.name.slice(0, -5); + "function" == typeof TypedArray ? bytesPerElement = TypedArray.BYTES_PER_ELEMENT : TypedArray = null; for (let littleEndian = 0; littleEndian < 2; littleEndian++) { if (!littleEndian && 1 == bytesPerElement) continue; let sizeShift = 2 == bytesPerElement ? 1 : 4 == bytesPerElement ? 2 : 3; currentExtensions[littleEndian ? tag : tag - 4] = 1 == bytesPerElement || littleEndian == isLittleEndianMachine$1 ? buffer => { if (!TypedArray) throw new Error("Could not find typed array for code " + tag); - return new TypedArray(Uint8Array.prototype.slice.call(buffer, 0).buffer); + return currentDecoder.copyBuffers || 1 !== bytesPerElement && (2 !== bytesPerElement || 1 & buffer.byteOffset) && (4 !== bytesPerElement || 3 & buffer.byteOffset) && (8 !== bytesPerElement || 7 & buffer.byteOffset) ? new TypedArray(Uint8Array.prototype.slice.call(buffer, 0).buffer) : new TypedArray(buffer.buffer, buffer.byteOffset, buffer.byteLength); } : buffer => { if (!TypedArray) throw new Error("Could not find typed array for code " + tag); let dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength), elements = buffer.length >> sizeShift, ta = new TypedArray(elements), method = dv[dvMethod]; @@ -631,7 +656,7 @@ try { textEncoder = new TextEncoder; } catch (error) {} -const hasNodeBuffer = "undefined" != typeof Buffer, ByteArrayAllocate = hasNodeBuffer ? Buffer.allocUnsafeSlow : Uint8Array, ByteArray = hasNodeBuffer ? Buffer : Uint8Array, BlobConstructor = "undefined" == typeof Blob ? {} : Blob, MAX_BUFFER_SIZE = hasNodeBuffer ? 4294967296 : 2144337920; +const Buffer$1 = "object" == typeof globalThis && globalThis.Buffer, hasNodeBuffer = void 0 !== Buffer$1, ByteArrayAllocate = hasNodeBuffer ? Buffer$1.allocUnsafeSlow : Uint8Array, ByteArray = hasNodeBuffer ? Buffer$1 : Uint8Array, MAX_BUFFER_SIZE = hasNodeBuffer ? 4294967296 : 2144337920; let throwOnIterable, target, targetView, safeEnd, position = 0, bundledStrings = null; @@ -804,13 +829,7 @@ class Encoder extends Decoder { target[position++] = 121, target[position++] = length >> 8, target[position++] = 255 & length) : (headerSize < 5 && target.copyWithin(position + 5, position + 3, position + 3 + length), target[position++] = 122, targetView.setUint32(position, length), position += 4), position += length; - } else if ("number" === type) if (value >>> 0 === value) value < 24 ? target[position++] = value : value < 256 ? (target[position++] = 24, - target[position++] = value) : value < 65536 ? (target[position++] = 25, target[position++] = value >> 8, - target[position++] = 255 & value) : (target[position++] = 26, targetView.setUint32(position, value), - position += 4); else if (value >> 0 === value) value >= -24 ? target[position++] = 31 - value : value >= -256 ? (target[position++] = 56, - target[position++] = ~value) : value >= -65536 ? (target[position++] = 57, targetView.setUint16(position, ~value), - position += 2) : (target[position++] = 58, targetView.setUint32(position, ~value), - position += 4); else { + } else if ("number" === type) if (this.alwaysUseFloat || value >>> 0 !== value) if (this.alwaysUseFloat || value >> 0 !== value) { let useFloat32; if ((useFloat32 = this.useFloat32) > 0 && value < 4294967296 && value >= -2147483648) { let xShifted; @@ -818,7 +837,13 @@ class Encoder extends Decoder { position--; } target[position++] = 251, targetView.setFloat64(position, value), position += 8; - } else if ("object" === type) if (value) { + } else value >= -24 ? target[position++] = 31 - value : value >= -256 ? (target[position++] = 56, + target[position++] = ~value) : value >= -65536 ? (target[position++] = 57, targetView.setUint16(position, ~value), + position += 2) : (target[position++] = 58, targetView.setUint32(position, ~value), + position += 4); else value < 24 ? target[position++] = value : value < 256 ? (target[position++] = 24, + target[position++] = value) : value < 65536 ? (target[position++] = 25, target[position++] = value >> 8, + target[position++] = 255 & value) : (target[position++] = 26, targetView.setUint32(position, value), + position += 4); else if ("object" === type) if (value) { if (referenceMap) { let referee = referenceMap.get(value); if (referee) { @@ -862,10 +887,14 @@ class Encoder extends Decoder { for (let entry of value) encode(entry); return void (target[position++] = 255); } - if (value[Symbol.asyncIterator] || constructor === BlobConstructor) { + if (value[Symbol.asyncIterator] || isBlob(value)) { let error = new Error("Iterable/blob should be serialized as iterator"); throw error.iteratorNotHandled = !0, error; } + if (this.useToJSON && value.toJSON) { + const json = value.toJSON(); + if (json !== value) return encode(json); + } writeObject(value, !value.hasOwnProperty); } } else target[position++] = 246; else if ("boolean" === type) target[position++] = value ? 245 : 244; else if ("bigint" === type) { @@ -884,7 +913,7 @@ class Encoder extends Decoder { if (length < 24 ? target[position++] = 160 | length : length < 256 ? (target[position++] = 184, target[position++] = length) : length < 65536 ? (target[position++] = 185, target[position++] = length >> 8, target[position++] = 255 & length) : (target[position++] = 186, targetView.setUint32(position, length), - position += 4), encoder.keyMap) for (let i = 0; i < length; i++) encode(encodeKey(keys[i])), + position += 4), encoder.keyMap) for (let i = 0; i < length; i++) encode(encoder.encodeKey(keys[i])), encode(vals[i]); else for (let i = 0; i < length; i++) encode(keys[i]), encode(vals[i]); } : (object, safePrototype) => { target[position++] = 185; @@ -952,7 +981,8 @@ class Encoder extends Decoder { useRecords || encode(key), value && "object" == typeof value ? iterateProperties[key] ? yield* encodeObjectAsIterable(value, iterateProperties[key]) : yield* tryEncode(value, iterateProperties, key) : encode(value); } } else if (constructor === Array) { - writeArrayHeader(object.length); + let length = object.length; + writeArrayHeader(length); for (let i = 0; i < length; i++) { let value = object[i]; value && ("object" == typeof value || position - start > chunkThreshold) ? iterateProperties.element ? yield* encodeObjectAsIterable(value, iterateProperties.element) : yield* tryEncode(value, iterateProperties, "element") : encode(value); @@ -961,7 +991,7 @@ class Encoder extends Decoder { target[position++] = 159; for (let value of object) value && ("object" == typeof value || position - start > chunkThreshold) ? iterateProperties.element ? yield* encodeObjectAsIterable(value, iterateProperties.element) : yield* tryEncode(value, iterateProperties, "element") : encode(value); target[position++] = 255; - } else constructor === BlobConstructor ? (writeEntityLength(object.size, 64), yield target.subarray(start, position), + } else isBlob(object) ? (writeEntityLength(object.size, 64), yield target.subarray(start, position), yield object, restartEncoding()) : object[Symbol.asyncIterator] ? (target[position++] = 159, yield target.subarray(start, position), yield object, restartEncoding(), target[position++] = 255) : encode(object); finalIterable && position > start ? yield target.subarray(start, position) : position - start > chunkThreshold && (yield target.subarray(start, position), @@ -987,7 +1017,7 @@ class Encoder extends Decoder { async function* encodeObjectAsAsyncIterable(value, iterateProperties) { for (let encodedValue of encodeObjectAsIterable(value, iterateProperties, !0)) { let constructor = encodedValue.constructor; - if (constructor === ByteArray || constructor === Uint8Array) yield encodedValue; else if (constructor === BlobConstructor) { + if (constructor === ByteArray || constructor === Uint8Array) yield encodedValue; else if (isBlob(encodedValue)) { let next, reader = encodedValue.stream().getReader(); for (;!(next = await reader.read()).done; ) yield next.value; } else if (encodedValue[Symbol.asyncIterator]) for await (let asyncValue of encodedValue) restartEncoding(), @@ -1038,6 +1068,14 @@ function writeArrayHeader(length) { position += 4); } +const BlobConstructor = "undefined" == typeof Blob ? function() {} : Blob; + +function isBlob(object) { + if (object instanceof BlobConstructor) return !0; + let tag = object[Symbol.toStringTag]; + return "Blob" === tag || "File" === tag; +} + function findRepetitiveStrings(value, packedValues) { switch (typeof value) { case "string": @@ -1075,7 +1113,7 @@ function typedArrayEncoder(tag, size) { tag, encode: function(typedArray, encode) { let length = typedArray.byteLength, offset = typedArray.byteOffset || 0, buffer = typedArray.buffer || typedArray; - encode(hasNodeBuffer ? Buffer.from(buffer, offset, length) : new Uint8Array(buffer, offset, length)); + encode(hasNodeBuffer ? Buffer$1.from(buffer, offset, length) : new Uint8Array(buffer, offset, length)); } }; } diff --git a/delivery/common/cwt/examples/cwt-hs256/cwt.js b/delivery/common/cwt/examples/cwt-hs256/cwt.js index caafead0..a207206b 100644 --- a/delivery/common/cwt/examples/cwt-hs256/cwt.js +++ b/delivery/common/cwt/examples/cwt-hs256/cwt.js @@ -1,21 +1,47 @@ -/** @preserve @version 1.1.1 */ +/** @preserve @version 1.2.0 */ import { crypto } from "crypto"; -import { Encoder, Decoder } from "./cbor-x.js"; +import { Encoder, Decoder, Tag } from "./cbor-x.js"; + +import { logger } from "log"; const COSE_Mac0 = 17, COSE_Mac = 97, COSE_Sign = 98, COSE_Sign1 = 18, COSE_Encrypt0 = 16, COSE_Encrypt = 96, coseAlgTags = { - 5: "HMAC 256/256", - "-7": "ES256" -}, HeaderLabelToKey_alg = 1, HeaderLabelToKey_crit = 2, claimsLabelToKey_iss = 1, claimsLabelToKey_sub = 2, claimsLabelToKey_aud = 3, claimsLabelToKey_exp = 4, claimsLabelToKey_nbf = 5; + "-7": "ES256", + 5: "HS256", + "-37": "PS256" +}, HeaderLabels = { + alg: 1, + crit: 2, + kid: 4 +}, ClaimLabels = { + iss: 1, + sub: 2, + aud: 3, + exp: 4, + nbf: 5, + iat: 6, + cti: 7 +}, AlgorithmLabels = { + ES256: -7, + HS256: 5, + PS256: -37 +}; class Mac { - static async verifyHMAC(alg, message, signature, keys) { - if ("HMAC 256/256" === alg) { - let isSignVerified = !1; - for (const key of keys) if (isSignVerified = await crypto.subtle.verify({ - name: "HMAC" - }, key, signature, message), isSignVerified) return Promise.resolve(isSignVerified); - return Promise.resolve(isSignVerified); + static async verifyHMAC(alg, message, signature, key) { + if ("HS256" === alg) return await crypto.subtle.verify({ + name: "HMAC" + }, key, signature, message); + throw new Error(`Unsupported Algorithm, ${alg}`); + } + static async doSign(signaturePayload, key, alg) { + if ("HS256" === alg) { + return await crypto.subtle.sign({ + name: "HMAC", + hash: { + name: "SHA-256" + } + }, key, signaturePayload); } throw new Error(`Unsupported Algorithm, ${alg}`); } @@ -28,28 +54,68 @@ class CWTUtil { })).join(""); } static claimsTranslate(payload, labelsMap, translators) { - const result = {}; - for (const param in payload) { - const key = labelsMap[param] ? labelsMap[param] : param, theValue = translators && translators[key] ? translators[key](payload[param]) : payload[param]; - result[key] = theValue; + if (payload instanceof Map) { + const result = new Map; + for (const [k, v] of payload) { + const tK = labelsMap[k] ? labelsMap[k] : k, tV = translators && translators[tK] ? translators[tK](v) : v; + result.set(tK, tV); + } + return result; } - return result; + { + const result = new Map; + for (const param in payload) { + const key = labelsMap[param] ? labelsMap[param] : param, theValue = translators && translators[key] ? translators[key](payload[param]) : payload[param]; + result.set(key, theValue); + } + return result; + } + } + static isUint8ArrayEqual(arr1, arr2) { + return arr1 instanceof Uint8Array && (arr2 instanceof Uint8Array && (arr1.length === arr2.length && arr1.every(((value, index) => value === arr2[index])))); } } CWTUtil.EMPTY_BUFFER = new Uint8Array(0); class Sign { - static async verifySignature(alg, message, signature, keys) { - if ("ES256" === alg) { - let isSignVerified = !1; - for (const key of keys) if (isSignVerified = await crypto.subtle.verify({ + static async verifySignature(alg, message, signature, key) { + switch (alg) { + case "ES256": + return await crypto.subtle.verify({ name: "ECDSA", hash: "SHA-256" - }, key, signature, new Uint8Array(message)), isSignVerified) return Promise.resolve(isSignVerified); - return Promise.resolve(isSignVerified); + }, key, signature, new Uint8Array(message)); + + case "PS256": + return await crypto.subtle.verify({ + name: "RSA-PSS", + saltLength: 32 + }, key, signature, new Uint8Array(message)); + + default: + throw new Error(`Unsupported Algorithm, ${alg}`); + } + } + static async doSign(signaturePayload, key, alg) { + switch (alg) { + case "ES256": + return await crypto.subtle.sign({ + name: "ECDSA", + hash: { + name: "SHA-256" + } + }, key, signaturePayload); + + case "PS256": + return await crypto.subtle.sign({ + name: "RSA-PSS", + saltLength: 32 + }, key, signaturePayload); + + default: + throw new Error(`Unsupported Algorithm, ${alg}`); } - throw new Error(`Unsupported Algorithm, ${alg}`); } } @@ -61,10 +127,9 @@ class CWTValidator { constructor(cwtOptions) { this.cwtOptions = cwtOptions || {}, this.validateOptionTypes(); } - async validate(tokenBuf, keys, externalAAD) { + async validate(tokenBuf, verifiers) { if (!(tokenBuf instanceof Uint8Array)) throw new Error("Invalid token type, expected Uint8Array!"); - if (externalAAD && !(externalAAD instanceof Uint8Array)) throw new Error("Invalid externalAAD type, expected Uint8Array!"); - if (!Array.isArray(keys) || !keys.every((elem => void 0 !== elem.type || void 0 !== elem.extractable || null != elem.algorithm || null != elem.usages))) throw new Error("Invalid keys type, expected list of CryptoKey!"); + if (verifiers.find((elem => void 0 === elem.key || void 0 === elem.key.type || null == elem.key.algorithm || null == elem.key.usages || elem.externalAAD && !(elem.externalAAD instanceof Uint8Array) || elem.kid && !(elem.kid instanceof Uint8Array)))) throw new Error("Invalid verifiers, expected list of verifier with valid crypto key!"); let coseMessage = globalThis.cbordec.decode(tokenBuf), cwtType = this.cwtOptions.defaultCoseMsgType; if (this.cwtOptions.isCWTTagAdded) { if (61 !== coseMessage.tag) throw new Error("CWT malformed: expected CWT CBOR tag for the token!"); @@ -74,27 +139,31 @@ class CWTValidator { if (cwtType = coseMessage.tag, ![ COSE_Mac0, COSE_Mac, COSE_Sign, COSE_Sign1, COSE_Encrypt, COSE_Encrypt0 ].includes(cwtType)) throw new Error("CWT malformed: invalid COSE CBOR tag!"); coseMessage = coseMessage.value; } - externalAAD || (externalAAD = CWTUtil.EMPTY_BUFFER); - const cwtJSON = await this.verifyCoseMessage(coseMessage, cwtType, keys, this.cwtOptions.headerValidation, externalAAD); + const cwtJSON = await this.verifyCoseMessage(coseMessage, cwtType, verifiers, this.cwtOptions.headerValidation); return this.validateClaims(cwtJSON.payload), cwtJSON; } - async verifyCoseMessage(coseMessage, cwtType, keys, headerValidation, externalAAD) { + async verifyCoseMessage(coseMessage, cwtType, verifiers, headerValidation) { switch (cwtType) { case COSE_Mac0: { if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR MAC0Tag, expected array of length 4!"); const [p, u, payload, tag] = coseMessage; let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; - pH = pH.size ? pH : CWTUtil.EMPTY_BUFFER; const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; let alg; - if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabelToKey_alg); else { + if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabels.alg); else { if (uH === CWTUtil.EMPTY_BUFFER) throw new Error("CWT malformed: unable to find algo field from CWT token."); - alg = u.get(HeaderLabelToKey_alg); + alg = uH.get(HeaderLabels.alg); } - alg = coseAlgTags[alg.toString()]; - const MACstructure = [ "MAC0", p, externalAAD, payload ], toBeMACed = globalThis.cborenc.encode(MACstructure); - if (!await Mac.verifyHMAC(alg, toBeMACed, tag, keys)) throw new Error("CWT token signature verification failed!"); + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER; + alg = coseAlgTags[alg]; + let isSignVerified = !1; + for (const verifier of verifiers) { + const MACstructure = [ "MAC0", pP, verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, payload ], toBeVerified = globalThis.cborenc.encode(MACstructure); + if (isSignVerified = await Mac.verifyHMAC(alg, toBeVerified, tag, verifier.key), + isSignVerified) break; + } + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); const decodedPayload = globalThis.cbordec.decode(payload); return Promise.resolve({ header: { @@ -108,18 +177,57 @@ class CWTValidator { case COSE_Sign1: { if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR COSE_Sign1, expected array of length 4!"); - const [p, u, payload, signer] = coseMessage; + const [p, u, payload, signature] = coseMessage; let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; pH = pH.size ? pH : CWTUtil.EMPTY_BUFFER; const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; let alg; - if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabelToKey_alg); else { + if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabels.alg); else { if (uH === CWTUtil.EMPTY_BUFFER) throw new Error("CWT malformed: unable to find algo field from CWT token."); - alg = u.get(HeaderLabelToKey_alg); + alg = u.get(HeaderLabels.alg); + } + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER; + alg = coseAlgTags[alg]; + let isSignVerified = !1; + for (const verifier of verifiers) { + const SigStructure = [ "Signature1", pP, verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, payload ], toBeVerified = globalThis.cborenc.encode(SigStructure); + if (isSignVerified = await Sign.verifySignature(alg, toBeVerified, signature, verifier.key), + isSignVerified) break; } - alg = coseAlgTags[alg.toString()]; - const SigStructure = [ "Signature1", p, externalAAD, payload ], toBeVeried = globalThis.cborenc.encode(SigStructure); - if (!await Sign.verifySignature(alg, toBeVeried, signer, keys)) throw new Error("CWT token signature verification failed!"); + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); + const decodedPayload = globalThis.cbordec.decode(payload); + return Promise.resolve({ + header: { + p: pH, + u: uH + }, + payload: decodedPayload + }); + } + + case COSE_Sign: + { + if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR COSE_Sign, expected array of length 4!"); + const [p, u, payload, signatures] = coseMessage; + let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; + const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; + headerValidation && this.validateHeader(pH, !0); + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER, isSignVerified = !1; + for (const signature of signatures) { + let [signP, signU, sign] = signature; + const verifier = this.getVerifier(signU, verifiers); + if (verifier) { + const externalAAD = verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, signerPMap = signP.length ? globalThis.cborenc.decode(signP) : CWTUtil.EMPTY_BUFFER; + signP = signerPMap.size ? signP : CWTUtil.EMPTY_BUFFER; + const alg = signerPMap.get ? signerPMap.get(HeaderLabels.alg) : pH.get ? pH.get(HeaderLabels.alg) : void 0; + if (alg) { + const SigStructure = [ "Signature", pP, signP, externalAAD, payload ], toBeVerified = globalThis.cborenc.encode(SigStructure); + if (isSignVerified = await Sign.verifySignature(coseAlgTags[alg], toBeVerified, sign, verifier.key), + isSignVerified) break; + } else logger.error(`Unable to find alg in CWT token for kid = ${verifier.kid}, hence skipping the verifier!`); + } + } + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); const decodedPayload = globalThis.cbordec.decode(payload); return Promise.resolve({ header: { @@ -136,7 +244,7 @@ class CWTValidator { } validateHeader(headers, pheader) { if (headers.size && pheader) { - const h = headers, crit = h.get(HeaderLabelToKey_crit); + const h = headers, crit = h.get(HeaderLabels.crit); if (Array.isArray(crit)) { if (0 === crit.length) throw new Error("CWT Malformed: malformed protected header, crit array cannot be empty!"); for (const e of crit) if (void 0 === h.get(e)) throw new Error("CWT Malformed: malformed protected header, crit labels are not part of protected header"); @@ -145,29 +253,29 @@ class CWTValidator { } validateClaims(decodedPayload) { if (this.cwtOptions.issuer) { - const iss = decodedPayload.get(claimsLabelToKey_iss); + const iss = decodedPayload.get(ClaimLabels.iss); if (iss && this.cwtOptions.issuer !== iss) throw new Error(`CWT malformed: invalid iss, expected ${this.cwtOptions.issuer}`); } if (this.cwtOptions.subject) { - const sub = decodedPayload.get(claimsLabelToKey_sub); + const sub = decodedPayload.get(ClaimLabels.sub); if (sub && this.cwtOptions.subject !== sub) throw new Error(`CWT malformed: invalid sub, expected ${this.cwtOptions.subject}`); } if (this.cwtOptions.audience) { - const aud = decodedPayload.get(claimsLabelToKey_aud); + const aud = decodedPayload.get(ClaimLabels.aud); if (aud && (Array.isArray(aud) || "string" == typeof aud)) { if (!(Array.isArray(aud) ? aud : [ aud ]).includes(this.cwtOptions.audience)) throw new Error(`CWT malformed: invalid aud, expected ${this.cwtOptions.audience}`); } } const clockTimestamp = Math.floor(Date.now() / 1e3); if (!1 === this.cwtOptions.ignoreExpiration) { - const exp = decodedPayload.get(claimsLabelToKey_exp); + const exp = decodedPayload.get(ClaimLabels.exp); if (exp) { if ("number" != typeof exp) throw new Error("CWT malformed: exp must be number"); if (clockTimestamp > exp + (this.cwtOptions.clockTolerance || 0)) throw new Error("CWT token has been expired"); } } if (!1 === this.cwtOptions.ignoreNotBefore) { - const nbf = decodedPayload.get(claimsLabelToKey_nbf); + const nbf = decodedPayload.get(ClaimLabels.nbf); if (nbf) { if ("number" != typeof nbf) throw new Error("CWT malformed: nbf must be number"); if (nbf > clockTimestamp + (this.cwtOptions.clockTolerance || 0)) throw new Error("CWT is not active"); @@ -187,6 +295,66 @@ class CWTValidator { if (void 0 !== this.cwtOptions.clockTolerance && "number" != typeof this.cwtOptions.clockTolerance) throw new Error("Invalid cwtOptions: clockTolerance must be number"); this.cwtOptions.clockTolerance = 60; } + getVerifier(signU, verifiers) { + const kid = signU.get(HeaderLabels.kid); + for (const verifier of verifiers) if (CWTUtil.isUint8ArrayEqual(kid, verifier.kid)) return verifier; + return null; + } +} + +class CWTGenerator { + static async mac(claims, signer, contentHeader, recipients, options) { + if (!claims) throw new Error("Invalid claims type, cannot be null or undefined!"); + if (void 0 === signer.key || void 0 === signer.key.type || null == signer.key.algorithm || null == signer.key.usages || signer.externalAAD && !(signer.externalAAD instanceof Uint8Array)) throw new Error("Invalid signer, expected signer with valid crypto key!"); + if (!contentHeader) throw new Error("Invalid contentHeader type, cannot be null or undefined!"); + options = options || {}; + let pH = contentHeader && contentHeader.p ? contentHeader.p : new Map, uH = contentHeader && contentHeader.u ? contentHeader.u : new Map, protectedHeader = 0 === pH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(pH), alg = pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader!"); + let result, payload = globalThis.cborenc.encode(claims); + if (Array.isArray(recipients)) { + if (0 === recipients.length) throw new Error("No recipients found, there has to be atleast one recipients!"); + if (recipients.length > 0) throw new Error("Mac with recipients is not currently supported!"); + } else { + const MACstructure = [ "MAC0", protectedHeader, signer.externalAAD || CWTUtil.EMPTY_BUFFER, payload ]; + let signed = globalThis.cborenc.encode(MACstructure); + result = [ protectedHeader, uH, payload, await Mac.doSign(signed, signer.key, coseAlgTags[alg]) ], + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(result, COSE_Mac0) : result; + } + return result = options.isCWTTagAdded ? new Tag(result, 61) : result, globalThis.cborenc.encode(result); + } + static async sign(claims, signers, contentHeader, options) { + if (!claims) throw new Error("Invalid claims type, cannot be null or undefined!"); + if (!signers || Array.isArray(signers) && 0 == signers.length) throw new Error("Invalid signers, cannot be null or undefined, requies at atleast one signer!"); + if (Array.isArray(signers)) { + if (signers.find((elem => void 0 === elem.key || void 0 === elem.key.type || null == elem.key.algorithm || null == elem.key.usages || elem.externalAAD && !(elem.externalAAD instanceof Uint8Array)))) throw new Error("Invalid signers, expected list of signers with valid crypto key!"); + } else if (void 0 === signers.key || void 0 === signers.key.type || null == signers.key.algorithm || null == signers.key.usages || signers.externalAAD && !(signers.externalAAD instanceof Uint8Array)) throw new Error("Invalid signer, expected signer with valid crypto key!"); + options = options || {}; + let result, pH = contentHeader && contentHeader.p ? contentHeader.p : new Map, uH = contentHeader && contentHeader.u ? contentHeader.u : new Map, protectedHeader = 0 === pH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(pH), payload = globalThis.cborenc.encode(claims); + if (Array.isArray(signers)) { + let signatures = Array(); + for (const signer of signers) { + const externalAAD = signer.externalAAD || CWTUtil.EMPTY_BUFFER; + let signPH = signer.p ? signer.p : new Map, signUH = signer.u ? signer.u : new Map, alg = signPH.get(HeaderLabels.alg) || pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader or Signer object!"); + let signprotectedHeader = 0 === signPH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(signPH); + const SigStructure = [ "Signature", protectedHeader, signprotectedHeader, externalAAD, payload ]; + let signaturePayload = globalThis.cborenc.encode(SigStructure); + const sig = await Sign.doSign(signaturePayload, signer.key, coseAlgTags[alg]); + signatures.push([ signprotectedHeader, signUH, sig ]); + } + let signed = [ protectedHeader, uH, payload, signatures ]; + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(signed, COSE_Sign) : signed; + } else { + const externalAAD = signers.externalAAD || CWTUtil.EMPTY_BUFFER; + let alg = pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader!"); + const SigStructure = [ "Signature1", protectedHeader, externalAAD, payload ]; + let signaturePayload = globalThis.cborenc.encode(SigStructure); + let signed = [ protectedHeader, uH, payload, await Sign.doSign(signaturePayload, signers.key, coseAlgTags[alg]) ]; + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(signed, COSE_Sign1) : signed; + } + return result = options.isCWTTagAdded ? new Tag(result, 61) : result, globalThis.cborenc.encode(result); + } } -export { CWTUtil, CWTValidator }; +export { AlgorithmLabels, CWTGenerator, CWTUtil, CWTValidator, ClaimLabels, HeaderLabels }; diff --git a/delivery/common/cwt/examples/cwt-hs256/main.js b/delivery/common/cwt/examples/cwt-hs256/main.js index c603f1a7..e8bb984b 100644 --- a/delivery/common/cwt/examples/cwt-hs256/main.js +++ b/delivery/common/cwt/examples/cwt-hs256/main.js @@ -1,42 +1,26 @@ import { logger } from 'log'; -import { CWTUtil, CWTValidator} from './cwt.js'; +import { CWTUtil, CWTValidator, CWTGenerator, AlgorithmLabels, ClaimLabels, HeaderLabels} from './cwt.js'; import { crypto } from 'crypto'; import { base16 } from 'encoding'; //Integer keys mapping for CWT payload. This mapping is application specific, However keys from 1-7 are reserved const claimsLabelMap = { - 1: 'iss', 2: 'sub', 3: 'aud', 4: 'exp', 5: 'nbf', - 6: 'iat', - 7: 'cti', - 200: 'wmver', - 201: 'wmvnd', - 202: 'wmidtyp', - 203: 'wmidfmt', - 204: 'wmpatlen', - 205: 'wmid', - 206: 'wmsegduration', - 207: 'wmidalg', - 208: 'wmidivlen', - 209: 'wmidivhex', - 210: 'wmidpid', - 211: 'wmidpalg', - 212: 'wmidkeyver', - 213: 'wmopid' + 6: 'iat' }; //advanced options for cwt validator const cwtOptions = { //perform header validation - headerValidation: true, + headerValidation: false, //check token expiry - ignoreExpiration: false, + ignoreExpiration: true, //check token nbf - ignoreNotBefore: false + ignoreNotBefore: true }; const cwtValidator = new CWTValidator(cwtOptions); @@ -44,8 +28,7 @@ const cwtValidator = new CWTValidator(cwtOptions); export async function onClientRequest (request) { try { - // Fetch hmac veification key from Propery Manager - const secretKey = request.getVariable('PMUSER_CWT_HMAC_KEY'); + const secretKey = '403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d79569388'; const sKey = await crypto.subtle.importKey( 'raw', base16.decode(secretKey, 'Uint8Array').buffer, @@ -54,23 +37,34 @@ export async function onClientRequest (request) { hash: 'SHA-256' }, false, - ['verify'] + ['sign','verify'] ); - //Fetch the Authorization header from request - let cwt = request.getHeader('Authorization'); - if (cwt){ - cwt = cwt[0]; - //replace auth scheme before validating - cwt = cwt.replace('Bearer ',''); - //Assumption: CWT token as passed as hex encoded in authorization header. We decode the hex to get the binary - const tokenBuf = base16.decode(cwt,'Uint8Array'); - const cwtJSON = await cwtValidator.validate(tokenBuf,[sKey]); - const claims = CWTUtil.claimsTranslate(Object.fromEntries(new Map(cwtJSON.payload)),claimsLabelMap); - logger.log('cwtJSON %s: ',JSON.stringify(claims)); - request.respondWith(200, {}, JSON.stringify(claims)); + + if (request.path == '/token' && request.method == 'POST') { + const claims = { iss: 'mde_dev@akamai.com', iat: Date.now(), sub: "subject@akamai.com", aud: ["cdn@akamai.com"], exp: Date.now() + 86400, nbf: Date.now() - 86400}; + const claimsSet = CWTUtil.claimsTranslate(claims, ClaimLabels); + const signer = { + key: sKey + }; + const cwtToken = await CWTGenerator.mac(claimsSet, signer, { p: CWTUtil.claimsTranslate({ alg: AlgorithmLabels.HS256 }, HeaderLabels)}); + const cwtTokenHex = base16.encode(new Uint8Array(cwtToken)); + request.respondWith(200, {}, cwtTokenHex); } else { - //Return bad request of authorization header is not found - request.respondWith(400, {}, 'Authorization header is missing!'); + //Fetch the Authorization header from request + let cwtToken = request.getHeader('Authorization'); + if (cwtToken) { + cwtToken = cwtToken[0]; + //replace auth scheme before validating + cwtToken = cwtToken.replace('Bearer ',''); + //Assumption: CWT token as passed as hex encoded in authorization header. We decode the hex to get the binary + const tokenBuf = base16.decode(cwtToken,'Uint8Array'); + const cwtJSON = await cwtValidator.validate(tokenBuf,[{ key: sKey }]); + const claims = CWTUtil.claimsTranslate(cwtJSON.payload,claimsLabelMap); + request.respondWith(200, {}, JSON.stringify(Object.fromEntries(claims))); + } else { + //Return bad request of authorization header is not found + request.respondWith(400, {}, 'Authorization header is missing!'); + } } } catch (error) { logger.log(error); diff --git a/delivery/common/cwt/examples/cwt-ps256/README.md b/delivery/common/cwt/examples/cwt-ps256/README.md new file mode 100644 index 00000000..ebdd6a10 --- /dev/null +++ b/delivery/common/cwt/examples/cwt-ps256/README.md @@ -0,0 +1,2 @@ +# Example +This example demonstrate verification of CWT token that was signed using PS256 algorithm. diff --git a/delivery/common/cwt/examples/cwt-ps256/bundle.json b/delivery/common/cwt/examples/cwt-ps256/bundle.json new file mode 100644 index 00000000..b6d2e882 --- /dev/null +++ b/delivery/common/cwt/examples/cwt-ps256/bundle.json @@ -0,0 +1,4 @@ +{ + "edgeworker-version": "0.1", + "description" : "CWT generator and validator example with ES256" +} diff --git a/delivery/common/cwt/examples/cwt-ps256/cbor-x.js b/delivery/common/cwt/examples/cwt-ps256/cbor-x.js new file mode 100644 index 00000000..05f1b05e --- /dev/null +++ b/delivery/common/cwt/examples/cwt-ps256/cbor-x.js @@ -0,0 +1,1247 @@ +let decoder, src, srcEnd; + +try { + decoder = new TextDecoder; +} catch (error) {} + +let position$1 = 0; + +const RECORD_DEFINITIONS_ID = 57342, RECORD_INLINE_ID = 57343, BUNDLED_STRINGS_ID = 57337, STOP_CODE = {}; + +let currentStructures, srcString, bundledStrings$1, referenceMap, packedValues, dataView, restoreMapsAsObject, currentDecoder = {}, srcStringStart = 0, srcStringEnd = 0, currentExtensions = [], currentExtensionRanges = [], defaultOptions = { + useRecords: !1, + mapsAsObjects: !0 +}, sequentialMode = !1, inlineObjectReadThreshold = 2; + +try { + new Function(""); +} catch (error) { + inlineObjectReadThreshold = 1 / 0; +} + +class Decoder { + constructor(options) { + if (options && (!options.keyMap && !options._keyMap || options.useRecords || (options.useRecords = !1, + options.mapsAsObjects = !0), !1 === options.useRecords && void 0 === options.mapsAsObjects && (options.mapsAsObjects = !0), + options.getStructures && (options.getShared = options.getStructures), options.getShared && !options.structures && ((options.structures = []).uninitialized = !0), + options.keyMap)) { + this.mapKey = new Map; + for (let [k, v] of Object.entries(options.keyMap)) this.mapKey.set(v, k); + } + Object.assign(this, options); + } + decodeKey(key) { + return this.keyMap && this.mapKey.get(key) || key; + } + encodeKey(key) { + return this.keyMap && this.keyMap.hasOwnProperty(key) ? this.keyMap[key] : key; + } + encodeKeys(rec) { + if (!this._keyMap) return rec; + let map = new Map; + for (let [k, v] of Object.entries(rec)) map.set(this._keyMap.hasOwnProperty(k) ? this._keyMap[k] : k, v); + return map; + } + decodeKeys(map) { + if (!this._keyMap || "Map" != map.constructor.name) return map; + if (!this._mapKey) { + this._mapKey = new Map; + for (let [k, v] of Object.entries(this._keyMap)) this._mapKey.set(v, k); + } + let res = {}; + return map.forEach(((v, k) => res[safeKey(this._mapKey.has(k) ? this._mapKey.get(k) : k)] = v)), + res; + } + mapDecode(source, end) { + let res = this.decode(source); + return this._keyMap && "Array" === res.constructor.name ? res.map((r => this.decodeKeys(r))) : res; + } + decode(source, end) { + if (src) return saveState((() => (clearSource(), this ? this.decode(source, end) : Decoder.prototype.decode.call(defaultOptions, source, end)))); + srcEnd = end > -1 ? end : source.length, position$1 = 0, srcStringEnd = 0, srcString = null, + bundledStrings$1 = null, src = source; + try { + dataView = source.dataView || (source.dataView = new DataView(source.buffer, source.byteOffset, source.byteLength)); + } catch (error) { + if (src = null, source instanceof Uint8Array) throw error; + throw new Error("Source must be a Uint8Array or Buffer but was a " + (source && "object" == typeof source ? source.constructor.name : typeof source)); + } + if (this instanceof Decoder) { + if (currentDecoder = this, packedValues = this.sharedValues && (this.pack ? new Array(this.maxPrivatePackedValues || 16).concat(this.sharedValues) : this.sharedValues), + this.structures) return currentStructures = this.structures, checkedRead(); + (!currentStructures || currentStructures.length > 0) && (currentStructures = []); + } else currentDecoder = defaultOptions, (!currentStructures || currentStructures.length > 0) && (currentStructures = []), + packedValues = null; + return checkedRead(); + } + decodeMultiple(source, forEach) { + let values, lastPosition = 0; + try { + let size = source.length; + sequentialMode = !0; + let value = this ? this.decode(source, size) : defaultDecoder.decode(source, size); + if (!forEach) { + for (values = [ value ]; position$1 < size; ) lastPosition = position$1, values.push(checkedRead()); + return values; + } + if (!1 === forEach(value)) return; + for (;position$1 < size; ) if (lastPosition = position$1, !1 === forEach(checkedRead())) return; + } catch (error) { + throw error.lastPosition = lastPosition, error.values = values, error; + } finally { + sequentialMode = !1, clearSource(); + } + } +} + +function checkedRead() { + try { + let result = read(); + if (bundledStrings$1) { + if (position$1 >= bundledStrings$1.postBundlePosition) { + let error = new Error("Unexpected bundle position"); + throw error.incomplete = !0, error; + } + position$1 = bundledStrings$1.postBundlePosition, bundledStrings$1 = null; + } + if (position$1 == srcEnd) currentStructures = null, src = null, referenceMap && (referenceMap = null); else { + if (position$1 > srcEnd) { + let error = new Error("Unexpected end of CBOR data"); + throw error.incomplete = !0, error; + } + if (!sequentialMode) throw new Error("Data read, but end of buffer not reached"); + } + return result; + } catch (error) { + throw clearSource(), (error instanceof RangeError || error.message.startsWith("Unexpected end of buffer")) && (error.incomplete = !0), + error; + } +} + +function read() { + let token = src[position$1++], majorType = token >> 5; + if (token &= 31, token > 23) switch (token) { + case 24: + token = src[position$1++]; + break; + + case 25: + if (7 == majorType) return function() { + let byte0 = src[position$1++], byte1 = src[position$1++], exponent = (127 & byte0) >> 2; + if (31 === exponent) return byte1 || 3 & byte0 ? NaN : 128 & byte0 ? -1 / 0 : 1 / 0; + if (0 === exponent) { + let abs = ((3 & byte0) << 8 | byte1) / (1 << 24); + return 128 & byte0 ? -abs : abs; + } + return u8Array[3] = 128 & byte0 | 56 + (exponent >> 1), u8Array[2] = (7 & byte0) << 5 | byte1 >> 3, + u8Array[1] = byte1 << 5, u8Array[0] = 0, f32Array[0]; + }(); + token = dataView.getUint16(position$1), position$1 += 2; + break; + + case 26: + if (7 == majorType) { + let value = dataView.getFloat32(position$1); + if (currentDecoder.useFloat32 > 2) { + let multiplier = mult10[(127 & src[position$1]) << 1 | src[position$1 + 1] >> 7]; + return position$1 += 4, (multiplier * value + (value > 0 ? .5 : -.5) >> 0) / multiplier; + } + return position$1 += 4, value; + } + token = dataView.getUint32(position$1), position$1 += 4; + break; + + case 27: + if (7 == majorType) { + let value = dataView.getFloat64(position$1); + return position$1 += 8, value; + } + if (majorType > 1) { + if (dataView.getUint32(position$1) > 0) throw new Error("JavaScript does not support arrays, maps, or strings with length over 4294967295"); + token = dataView.getUint32(position$1 + 4); + } else currentDecoder.int64AsNumber ? (token = 4294967296 * dataView.getUint32(position$1), + token += dataView.getUint32(position$1 + 4)) : token = dataView.getBigUint64(position$1); + position$1 += 8; + break; + + case 31: + switch (majorType) { + case 2: + case 3: + throw new Error("Indefinite length not supported for byte or text strings"); + + case 4: + let value, array = [], i = 0; + for (;(value = read()) != STOP_CODE; ) array[i++] = value; + return 4 == majorType ? array : 3 == majorType ? array.join("") : Buffer.concat(array); + + case 5: + let key; + if (currentDecoder.mapsAsObjects) { + let object = {}; + if (currentDecoder.keyMap) for (;(key = read()) != STOP_CODE; ) object[safeKey(currentDecoder.decodeKey(key))] = read(); else for (;(key = read()) != STOP_CODE; ) object[safeKey(key)] = read(); + return object; + } + { + restoreMapsAsObject && (currentDecoder.mapsAsObjects = !0, restoreMapsAsObject = !1); + let map = new Map; + if (currentDecoder.keyMap) for (;(key = read()) != STOP_CODE; ) map.set(currentDecoder.decodeKey(key), read()); else for (;(key = read()) != STOP_CODE; ) map.set(key, read()); + return map; + } + + case 7: + return STOP_CODE; + + default: + throw new Error("Invalid major type for indefinite length " + majorType); + } + + default: + throw new Error("Unknown token " + token); + } + switch (majorType) { + case 0: + return token; + + case 1: + return ~token; + + case 2: + return length = token, currentDecoder.copyBuffers ? Uint8Array.prototype.slice.call(src, position$1, position$1 += length) : src.subarray(position$1, position$1 += length); + + case 3: + if (srcStringEnd >= position$1) return srcString.slice(position$1 - srcStringStart, (position$1 += token) - srcStringStart); + if (0 == srcStringEnd && srcEnd < 140 && token < 32) { + let string = token < 16 ? shortStringInJS(token) : function(length) { + let start = position$1, bytes = new Array(length); + for (let i = 0; i < length; i++) { + const byte = src[position$1++]; + if ((128 & byte) > 0) return void (position$1 = start); + bytes[i] = byte; + } + return fromCharCode.apply(String, bytes); + }(token); + if (null != string) return string; + } + return readFixedString(token); + + case 4: + let array = new Array(token); + for (let i = 0; i < token; i++) array[i] = read(); + return array; + + case 5: + if (currentDecoder.mapsAsObjects) { + let object = {}; + if (currentDecoder.keyMap) for (let i = 0; i < token; i++) object[safeKey(currentDecoder.decodeKey(read()))] = read(); else for (let i = 0; i < token; i++) object[safeKey(read())] = read(); + return object; + } + { + restoreMapsAsObject && (currentDecoder.mapsAsObjects = !0, restoreMapsAsObject = !1); + let map = new Map; + if (currentDecoder.keyMap) for (let i = 0; i < token; i++) map.set(currentDecoder.decodeKey(read()), read()); else for (let i = 0; i < token; i++) map.set(read(), read()); + return map; + } + + case 6: + if (token >= BUNDLED_STRINGS_ID) { + let structure = currentStructures[8191 & token]; + if (structure) return structure.read || (structure.read = createStructureReader(structure)), + structure.read(); + if (token < 65536) { + if (token == RECORD_INLINE_ID) { + let length = readJustLength(), id = read(), structure = read(); + recordDefinition(id, structure); + let object = {}; + if (currentDecoder.keyMap) for (let i = 2; i < length; i++) { + object[safeKey(currentDecoder.decodeKey(structure[i - 2]))] = read(); + } else for (let i = 2; i < length; i++) { + object[safeKey(structure[i - 2])] = read(); + } + return object; + } + if (token == RECORD_DEFINITIONS_ID) { + let length = readJustLength(), id = read(); + for (let i = 2; i < length; i++) recordDefinition(id++, read()); + return read(); + } + if (token == BUNDLED_STRINGS_ID) return function() { + let length = readJustLength(), bundlePosition = position$1 + read(); + for (let i = 2; i < length; i++) { + let bundleLength = readJustLength(); + position$1 += bundleLength; + } + let dataPosition = position$1; + return position$1 = bundlePosition, bundledStrings$1 = [ readStringJS(readJustLength()), readStringJS(readJustLength()) ], + bundledStrings$1.position0 = 0, bundledStrings$1.position1 = 0, bundledStrings$1.postBundlePosition = position$1, + position$1 = dataPosition, read(); + }(); + if (currentDecoder.getShared && (loadShared(), structure = currentStructures[8191 & token], + structure)) return structure.read || (structure.read = createStructureReader(structure)), + structure.read(); + } + } + let extension = currentExtensions[token]; + if (extension) return extension.handlesRead ? extension(read) : extension(read()); + { + let input = read(); + for (let i = 0; i < currentExtensionRanges.length; i++) { + let value = currentExtensionRanges[i](token, input); + if (void 0 !== value) return value; + } + return new Tag(input, token); + } + + case 7: + switch (token) { + case 20: + return !1; + + case 21: + return !0; + + case 22: + return null; + + case 23: + return; + + default: + let packedValue = (packedValues || getPackedValues())[token]; + if (void 0 !== packedValue) return packedValue; + throw new Error("Unknown token " + token); + } + + default: + if (isNaN(token)) { + let error = new Error("Unexpected end of CBOR data"); + throw error.incomplete = !0, error; + } + throw new Error("Unknown CBOR token " + token); + } + var length; +} + +const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/; + +function createStructureReader(structure) { + return structure.slowReads = 0, function() { + let length = src[position$1++]; + if (length &= 31, length > 23) switch (length) { + case 24: + length = src[position$1++]; + break; + + case 25: + length = dataView.getUint16(position$1), position$1 += 2; + break; + + case 26: + length = dataView.getUint32(position$1), position$1 += 4; + break; + + default: + throw new Error("Expected array header, but got " + src[position$1 - 1]); + } + let compiledReader = this.compiledReader; + for (;compiledReader; ) { + if (compiledReader.propertyCount === length) return compiledReader(read); + compiledReader = compiledReader.next; + } + if (this.slowReads++ >= inlineObjectReadThreshold) { + let array = this.length == length ? this : this.slice(0, length); + return compiledReader = currentDecoder.keyMap ? new Function("r", "return {" + array.map((k => currentDecoder.decodeKey(k))).map((k => validName.test(k) ? safeKey(k) + ":r()" : "[" + JSON.stringify(k) + "]:r()")).join(",") + "}") : new Function("r", "return {" + array.map((key => validName.test(key) ? safeKey(key) + ":r()" : "[" + JSON.stringify(key) + "]:r()")).join(",") + "}"), + this.compiledReader && (compiledReader.next = this.compiledReader), compiledReader.propertyCount = length, + this.compiledReader = compiledReader, compiledReader(read); + } + let object = {}; + if (currentDecoder.keyMap) for (let i = 0; i < length; i++) object[safeKey(currentDecoder.decodeKey(this[i]))] = read(); else for (let i = 0; i < length; i++) object[safeKey(this[i])] = read(); + return object; + }; +} + +function safeKey(key) { + if ("string" == typeof key) return "__proto__" === key ? "__proto_" : key; + if ("object" != typeof key) return key.toString(); + throw new Error("Invalid property name type " + typeof key); +} + +let readFixedString = readStringJS, isNativeAccelerationEnabled = !1; + +function readStringJS(length) { + let result; + if (length < 16 && (result = shortStringInJS(length))) return result; + if (length > 64 && decoder) return decoder.decode(src.subarray(position$1, position$1 += length)); + const end = position$1 + length, units = []; + for (result = ""; position$1 < end; ) { + const byte1 = src[position$1++]; + if (0 == (128 & byte1)) units.push(byte1); else if (192 == (224 & byte1)) { + const byte2 = 63 & src[position$1++]; + units.push((31 & byte1) << 6 | byte2); + } else if (224 == (240 & byte1)) { + const byte2 = 63 & src[position$1++], byte3 = 63 & src[position$1++]; + units.push((31 & byte1) << 12 | byte2 << 6 | byte3); + } else if (240 == (248 & byte1)) { + let unit = (7 & byte1) << 18 | (63 & src[position$1++]) << 12 | (63 & src[position$1++]) << 6 | 63 & src[position$1++]; + unit > 65535 && (unit -= 65536, units.push(unit >>> 10 & 1023 | 55296), unit = 56320 | 1023 & unit), + units.push(unit); + } else units.push(byte1); + units.length >= 4096 && (result += fromCharCode.apply(String, units), units.length = 0); + } + return units.length > 0 && (result += fromCharCode.apply(String, units)), result; +} + +let fromCharCode = String.fromCharCode; + +function shortStringInJS(length) { + if (length < 4) { + if (length < 2) { + if (0 === length) return ""; + { + let a = src[position$1++]; + return (128 & a) > 1 ? void (position$1 -= 1) : fromCharCode(a); + } + } + { + let a = src[position$1++], b = src[position$1++]; + if ((128 & a) > 0 || (128 & b) > 0) return void (position$1 -= 2); + if (length < 3) return fromCharCode(a, b); + let c = src[position$1++]; + return (128 & c) > 0 ? void (position$1 -= 3) : fromCharCode(a, b, c); + } + } + { + let a = src[position$1++], b = src[position$1++], c = src[position$1++], d = src[position$1++]; + if ((128 & a) > 0 || (128 & b) > 0 || (128 & c) > 0 || (128 & d) > 0) return void (position$1 -= 4); + if (length < 6) { + if (4 === length) return fromCharCode(a, b, c, d); + { + let e = src[position$1++]; + return (128 & e) > 0 ? void (position$1 -= 5) : fromCharCode(a, b, c, d, e); + } + } + if (length < 8) { + let e = src[position$1++], f = src[position$1++]; + if ((128 & e) > 0 || (128 & f) > 0) return void (position$1 -= 6); + if (length < 7) return fromCharCode(a, b, c, d, e, f); + let g = src[position$1++]; + return (128 & g) > 0 ? void (position$1 -= 7) : fromCharCode(a, b, c, d, e, f, g); + } + { + let e = src[position$1++], f = src[position$1++], g = src[position$1++], h = src[position$1++]; + if ((128 & e) > 0 || (128 & f) > 0 || (128 & g) > 0 || (128 & h) > 0) return void (position$1 -= 8); + if (length < 10) { + if (8 === length) return fromCharCode(a, b, c, d, e, f, g, h); + { + let i = src[position$1++]; + return (128 & i) > 0 ? void (position$1 -= 9) : fromCharCode(a, b, c, d, e, f, g, h, i); + } + } + if (length < 12) { + let i = src[position$1++], j = src[position$1++]; + if ((128 & i) > 0 || (128 & j) > 0) return void (position$1 -= 10); + if (length < 11) return fromCharCode(a, b, c, d, e, f, g, h, i, j); + let k = src[position$1++]; + return (128 & k) > 0 ? void (position$1 -= 11) : fromCharCode(a, b, c, d, e, f, g, h, i, j, k); + } + { + let i = src[position$1++], j = src[position$1++], k = src[position$1++], l = src[position$1++]; + if ((128 & i) > 0 || (128 & j) > 0 || (128 & k) > 0 || (128 & l) > 0) return void (position$1 -= 12); + if (length < 14) { + if (12 === length) return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l); + { + let m = src[position$1++]; + return (128 & m) > 0 ? void (position$1 -= 13) : fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m); + } + } + { + let m = src[position$1++], n = src[position$1++]; + if ((128 & m) > 0 || (128 & n) > 0) return void (position$1 -= 14); + if (length < 15) return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n); + let o = src[position$1++]; + return (128 & o) > 0 ? void (position$1 -= 15) : fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o); + } + } + } + } +} + +let f32Array = new Float32Array(1), u8Array = new Uint8Array(f32Array.buffer, 0, 4); + +new Array(4096); + +class Tag { + constructor(value, tag) { + this.value = value, this.tag = tag; + } +} + +currentExtensions[0] = dateString => new Date(dateString), currentExtensions[1] = epochSec => new Date(Math.round(1e3 * epochSec)), +currentExtensions[2] = buffer => { + let value = BigInt(0); + for (let i = 0, l = buffer.byteLength; i < l; i++) value = BigInt(buffer[i]) + value << BigInt(8); + return value; +}, currentExtensions[3] = buffer => BigInt(-1) - currentExtensions[2](buffer), currentExtensions[4] = fraction => +(fraction[1] + "e" + fraction[0]), +currentExtensions[5] = fraction => fraction[1] * Math.exp(fraction[0] * Math.log(2)); + +const recordDefinition = (id, structure) => { + let existingStructure = currentStructures[id -= 57344]; + existingStructure && existingStructure.isShared && ((currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure), + currentStructures[id] = structure, structure.read = createStructureReader(structure); +}; + +currentExtensions[105] = data => { + let length = data.length, structure = data[1]; + recordDefinition(data[0], structure); + let object = {}; + for (let i = 2; i < length; i++) { + object[safeKey(structure[i - 2])] = data[i]; + } + return object; +}, currentExtensions[14] = value => bundledStrings$1 ? bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 += value) : new Tag(value, 14), +currentExtensions[15] = value => bundledStrings$1 ? bundledStrings$1[1].slice(bundledStrings$1.position1, bundledStrings$1.position1 += value) : new Tag(value, 15); + +let glbl = { + Error, + RegExp +}; + +currentExtensions[27] = data => (glbl[data[0]] || Error)(data[1], data[2]); + +const packedTable = read => { + if (132 != src[position$1++]) { + let error = new Error("Packed values structure must be followed by a 4 element array"); + throw src.length < position$1 && (error.incomplete = !0), error; + } + let newPackedValues = read(); + if (!newPackedValues || !newPackedValues.length) { + let error = new Error("Packed values structure must be followed by a 4 element array"); + throw error.incomplete = !0, error; + } + return packedValues = packedValues ? newPackedValues.concat(packedValues.slice(newPackedValues.length)) : newPackedValues, + packedValues.prefixes = read(), packedValues.suffixes = read(), read(); +}; + +function combine(a, b) { + return "string" == typeof a ? a + b : a instanceof Array ? a.concat(b) : Object.assign({}, a, b); +} + +function getPackedValues() { + if (!packedValues) { + if (!currentDecoder.getShared) throw new Error("No packed values available"); + loadShared(); + } + return packedValues; +} + +packedTable.handlesRead = !0, currentExtensions[51] = packedTable, currentExtensions[6] = data => { + if (!packedValues) { + if (!currentDecoder.getShared) return new Tag(data, 6); + loadShared(); + } + if ("number" == typeof data) return packedValues[16 + (data >= 0 ? 2 * data : -2 * data - 1)]; + let error = new Error("No support for non-integer packed references yet"); + throw void 0 === data && (error.incomplete = !0), error; +}, currentExtensions[28] = read => { + referenceMap || (referenceMap = new Map, referenceMap.id = 0); + let target, id = referenceMap.id++; + target = src[position$1] >> 5 == 4 ? [] : {}; + let refEntry = { + target + }; + referenceMap.set(id, refEntry); + let targetProperties = read(); + return refEntry.used ? Object.assign(target, targetProperties) : (refEntry.target = targetProperties, + targetProperties); +}, currentExtensions[28].handlesRead = !0, currentExtensions[29] = id => { + let refEntry = referenceMap.get(id); + return refEntry.used = !0, refEntry.target; +}, currentExtensions[258] = array => new Set(array), (currentExtensions[259] = read => (currentDecoder.mapsAsObjects && (currentDecoder.mapsAsObjects = !1, +restoreMapsAsObject = !0), read())).handlesRead = !0; + +currentExtensionRanges.push(((tag, input) => tag >= 225 && tag <= 255 ? combine(getPackedValues().prefixes[tag - 224], input) : tag >= 28704 && tag <= 32767 ? combine(getPackedValues().prefixes[tag - 28672], input) : tag >= 1879052288 && tag <= 2147483647 ? combine(getPackedValues().prefixes[tag - 1879048192], input) : tag >= 216 && tag <= 223 ? combine(input, getPackedValues().suffixes[tag - 216]) : tag >= 27647 && tag <= 28671 ? combine(input, getPackedValues().suffixes[tag - 27639]) : tag >= 1811940352 && tag <= 1879048191 ? combine(input, getPackedValues().suffixes[tag - 1811939328]) : 1399353956 == tag ? { + packedValues, + structures: currentStructures.slice(0), + version: input +} : 55799 == tag ? input : void 0)); + +const isLittleEndianMachine$1 = 1 == new Uint8Array(new Uint16Array([ 1 ]).buffer)[0], typedArrays = [ Uint8Array, Uint8ClampedArray, Uint16Array, Uint32Array, "undefined" == typeof BigUint64Array ? { + name: "BigUint64Array" +} : BigUint64Array, Int8Array, Int16Array, Int32Array, "undefined" == typeof BigInt64Array ? { + name: "BigInt64Array" +} : BigInt64Array, Float32Array, Float64Array ], typedArrayTags = [ 64, 68, 69, 70, 71, 72, 77, 78, 79, 85, 86 ]; + +for (let i = 0; i < typedArrays.length; i++) registerTypedArray(typedArrays[i], typedArrayTags[i]); + +function registerTypedArray(TypedArray, tag) { + let bytesPerElement, dvMethod = "get" + TypedArray.name.slice(0, -5); + "function" == typeof TypedArray ? bytesPerElement = TypedArray.BYTES_PER_ELEMENT : TypedArray = null; + for (let littleEndian = 0; littleEndian < 2; littleEndian++) { + if (!littleEndian && 1 == bytesPerElement) continue; + let sizeShift = 2 == bytesPerElement ? 1 : 4 == bytesPerElement ? 2 : 3; + currentExtensions[littleEndian ? tag : tag - 4] = 1 == bytesPerElement || littleEndian == isLittleEndianMachine$1 ? buffer => { + if (!TypedArray) throw new Error("Could not find typed array for code " + tag); + return currentDecoder.copyBuffers || 1 !== bytesPerElement && (2 !== bytesPerElement || 1 & buffer.byteOffset) && (4 !== bytesPerElement || 3 & buffer.byteOffset) && (8 !== bytesPerElement || 7 & buffer.byteOffset) ? new TypedArray(Uint8Array.prototype.slice.call(buffer, 0).buffer) : new TypedArray(buffer.buffer, buffer.byteOffset, buffer.byteLength); + } : buffer => { + if (!TypedArray) throw new Error("Could not find typed array for code " + tag); + let dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength), elements = buffer.length >> sizeShift, ta = new TypedArray(elements), method = dv[dvMethod]; + for (let i = 0; i < elements; i++) ta[i] = method.call(dv, i << sizeShift, littleEndian); + return ta; + }; + } +} + +function readJustLength() { + let token = 31 & src[position$1++]; + if (token > 23) switch (token) { + case 24: + token = src[position$1++]; + break; + + case 25: + token = dataView.getUint16(position$1), position$1 += 2; + break; + + case 26: + token = dataView.getUint32(position$1), position$1 += 4; + } + return token; +} + +function loadShared() { + if (currentDecoder.getShared) { + let sharedData = saveState((() => (src = null, currentDecoder.getShared()))) || {}, updatedStructures = sharedData.structures || []; + currentDecoder.sharedVersion = sharedData.version, packedValues = currentDecoder.sharedValues = sharedData.packedValues, + !0 === currentStructures ? currentDecoder.structures = currentStructures = updatedStructures : currentStructures.splice.apply(currentStructures, [ 0, updatedStructures.length ].concat(updatedStructures)); + } +} + +function saveState(callback) { + let savedSrcEnd = srcEnd, savedPosition = position$1, savedSrcStringStart = srcStringStart, savedSrcStringEnd = srcStringEnd, savedSrcString = srcString, savedReferenceMap = referenceMap, savedBundledStrings = bundledStrings$1, savedSrc = new Uint8Array(src.slice(0, srcEnd)), savedStructures = currentStructures, savedDecoder = currentDecoder, savedSequentialMode = sequentialMode, value = callback(); + return srcEnd = savedSrcEnd, position$1 = savedPosition, srcStringStart = savedSrcStringStart, + srcStringEnd = savedSrcStringEnd, srcString = savedSrcString, referenceMap = savedReferenceMap, + bundledStrings$1 = savedBundledStrings, src = savedSrc, sequentialMode = savedSequentialMode, + currentStructures = savedStructures, currentDecoder = savedDecoder, dataView = new DataView(src.buffer, src.byteOffset, src.byteLength), + value; +} + +function clearSource() { + src = null, referenceMap = null, currentStructures = null; +} + +const mult10 = new Array(147); + +for (let i = 0; i < 256; i++) mult10[i] = +("1e" + Math.floor(45.15 - .30103 * i)); + +let defaultDecoder = new Decoder({ + useRecords: !1 +}); + +const decode = defaultDecoder.decode, decodeMultiple = defaultDecoder.decodeMultiple, FLOAT32_OPTIONS = { + NEVER: 0, + ALWAYS: 1, + DECIMAL_ROUND: 3, + DECIMAL_FIT: 4 +}; + +function roundFloat32(float32Number) { + f32Array[0] = float32Number; + let multiplier = mult10[(127 & u8Array[3]) << 1 | u8Array[2] >> 7]; + return (multiplier * float32Number + (float32Number > 0 ? .5 : -.5) >> 0) / multiplier; +} + +let textEncoder, extensions, extensionClasses; + +try { + textEncoder = new TextEncoder; +} catch (error) {} + +const Buffer$1 = "object" == typeof globalThis && globalThis.Buffer, hasNodeBuffer = void 0 !== Buffer$1, ByteArrayAllocate = hasNodeBuffer ? Buffer$1.allocUnsafeSlow : Uint8Array, ByteArray = hasNodeBuffer ? Buffer$1 : Uint8Array, MAX_BUFFER_SIZE = hasNodeBuffer ? 4294967296 : 2144337920; + +let throwOnIterable, target, targetView, safeEnd, position = 0, bundledStrings = null; + +const hasNonLatin = /[\u0080-\uFFFF]/, RECORD_SYMBOL = Symbol("record-id"); + +class Encoder extends Decoder { + constructor(options) { + let start, sharedStructures, hasSharedUpdate, structures, referenceMap; + super(options), this.offset = 0, options = options || {}; + let encodeUtf8 = ByteArray.prototype.utf8Write ? function(string, position, maxBytes) { + return target.utf8Write(string, position, maxBytes); + } : !(!textEncoder || !textEncoder.encodeInto) && function(string, position) { + return textEncoder.encodeInto(string, target.subarray(position)).written; + }, encoder = this, hasSharedStructures = options.structures || options.saveStructures, maxSharedStructures = options.maxSharedStructures; + if (null == maxSharedStructures && (maxSharedStructures = hasSharedStructures ? 128 : 0), + maxSharedStructures > 8190) throw new Error("Maximum maxSharedStructure is 8190"); + let isSequential = options.sequential; + isSequential && (maxSharedStructures = 0), this.structures || (this.structures = []), + this.saveStructures && (this.saveShared = this.saveStructures); + let samplingPackedValues, packedObjectMap, sharedPackedObjectMap, sharedValues = options.sharedValues; + if (sharedValues) { + sharedPackedObjectMap = Object.create(null); + for (let i = 0, l = sharedValues.length; i < l; i++) sharedPackedObjectMap[sharedValues[i]] = i; + } + let recordIdsToRemove = [], transitionsCount = 0, serializationsSinceTransitionRebuild = 0; + this.mapEncode = function(value, encodeOptions) { + if (this._keyMap && !this._mapped && "Array" === value.constructor.name) value = value.map((r => this.encodeKeys(r))); + return this.encode(value, encodeOptions); + }, this.encode = function(value, encodeOptions) { + if (target || (target = new ByteArrayAllocate(8192), targetView = new DataView(target.buffer, 0, 8192), + position = 0), safeEnd = target.length - 10, safeEnd - position < 2048 ? (target = new ByteArrayAllocate(target.length), + targetView = new DataView(target.buffer, 0, target.length), safeEnd = target.length - 10, + position = 0) : encodeOptions === REUSE_BUFFER_MODE && (position = position + 7 & 2147483640), + start = position, encoder.useSelfDescribedHeader && (targetView.setUint32(position, 3654940416), + position += 3), referenceMap = encoder.structuredClone ? new Map : null, encoder.bundleStrings && "string" != typeof value ? (bundledStrings = [], + bundledStrings.size = 1 / 0) : bundledStrings = null, sharedStructures = encoder.structures, + sharedStructures) { + if (sharedStructures.uninitialized) { + let sharedData = encoder.getShared() || {}; + encoder.structures = sharedStructures = sharedData.structures || [], encoder.sharedVersion = sharedData.version; + let sharedValues = encoder.sharedValues = sharedData.packedValues; + if (sharedValues) { + sharedPackedObjectMap = {}; + for (let i = 0, l = sharedValues.length; i < l; i++) sharedPackedObjectMap[sharedValues[i]] = i; + } + } + let sharedStructuresLength = sharedStructures.length; + if (sharedStructuresLength > maxSharedStructures && !isSequential && (sharedStructuresLength = maxSharedStructures), + !sharedStructures.transitions) { + sharedStructures.transitions = Object.create(null); + for (let i = 0; i < sharedStructuresLength; i++) { + let keys = sharedStructures[i]; + if (!keys) continue; + let nextTransition, transition = sharedStructures.transitions; + for (let j = 0, l = keys.length; j < l; j++) { + void 0 === transition[RECORD_SYMBOL] && (transition[RECORD_SYMBOL] = i); + let key = keys[j]; + nextTransition = transition[key], nextTransition || (nextTransition = transition[key] = Object.create(null)), + transition = nextTransition; + } + transition[RECORD_SYMBOL] = 1048576 | i; + } + } + isSequential || (sharedStructures.nextId = sharedStructuresLength); + } + if (hasSharedUpdate && (hasSharedUpdate = !1), structures = sharedStructures || [], + packedObjectMap = sharedPackedObjectMap, options.pack) { + let packedValues = new Map; + if (packedValues.values = [], packedValues.encoder = encoder, packedValues.maxValues = options.maxPrivatePackedValues || (sharedPackedObjectMap ? 16 : 1 / 0), + packedValues.objectMap = sharedPackedObjectMap || !1, packedValues.samplingPackedValues = samplingPackedValues, + findRepetitiveStrings(value, packedValues), packedValues.values.length > 0) { + target[position++] = 216, target[position++] = 51, writeArrayHeader(4); + let valuesArray = packedValues.values; + encode(valuesArray), writeArrayHeader(0), writeArrayHeader(0), packedObjectMap = Object.create(sharedPackedObjectMap || null); + for (let i = 0, l = valuesArray.length; i < l; i++) packedObjectMap[valuesArray[i]] = i; + } + } + throwOnIterable = encodeOptions & THROW_ON_ITERABLE; + try { + if (throwOnIterable) return; + if (encode(value), bundledStrings && writeBundles(start, encode), encoder.offset = position, + referenceMap && referenceMap.idsToInsert) { + position += 2 * referenceMap.idsToInsert.length, position > safeEnd && makeRoom(position), + encoder.offset = position; + let serialized = function(serialized, idsToInsert) { + let nextId, distanceToMove = 2 * idsToInsert.length, lastEnd = serialized.length - distanceToMove; + idsToInsert.sort(((a, b) => a.offset > b.offset ? 1 : -1)); + for (let id = 0; id < idsToInsert.length; id++) { + let referee = idsToInsert[id]; + referee.id = id; + for (let position of referee.references) serialized[position++] = id >> 8, serialized[position] = 255 & id; + } + for (;nextId = idsToInsert.pop(); ) { + let offset = nextId.offset; + serialized.copyWithin(offset + distanceToMove, offset, lastEnd), distanceToMove -= 2; + let position = offset + distanceToMove; + serialized[position++] = 216, serialized[position++] = 28, lastEnd = offset; + } + return serialized; + }(target.subarray(start, position), referenceMap.idsToInsert); + return referenceMap = null, serialized; + } + return encodeOptions & REUSE_BUFFER_MODE ? (target.start = start, target.end = position, + target) : target.subarray(start, position); + } finally { + if (sharedStructures) if (serializationsSinceTransitionRebuild < 10 && serializationsSinceTransitionRebuild++, + sharedStructures.length > maxSharedStructures && (sharedStructures.length = maxSharedStructures), + transitionsCount > 1e4) sharedStructures.transitions = null, serializationsSinceTransitionRebuild = 0, + transitionsCount = 0, recordIdsToRemove.length > 0 && (recordIdsToRemove = []); else if (recordIdsToRemove.length > 0 && !isSequential) { + for (let i = 0, l = recordIdsToRemove.length; i < l; i++) recordIdsToRemove[i][RECORD_SYMBOL] = void 0; + recordIdsToRemove = []; + } + if (hasSharedUpdate && encoder.saveShared) { + encoder.structures.length > maxSharedStructures && (encoder.structures = encoder.structures.slice(0, maxSharedStructures)); + let returnBuffer = target.subarray(start, position); + return !1 === encoder.updateSharedData() ? encoder.encode(value) : returnBuffer; + } + encodeOptions & RESET_BUFFER_MODE && (position = start); + } + }, this.findCommonStringsToPack = () => (samplingPackedValues = new Map, sharedPackedObjectMap || (sharedPackedObjectMap = Object.create(null)), + options => { + let threshold = options && options.threshold || 4, position = this.pack ? options.maxPrivatePackedValues || 16 : 0; + sharedValues || (sharedValues = this.sharedValues = []); + for (let [key, status] of samplingPackedValues) status.count > threshold && (sharedPackedObjectMap[key] = position++, + sharedValues.push(key), hasSharedUpdate = !0); + for (;this.saveShared && !1 === this.updateSharedData(); ) ; + samplingPackedValues = null; + }); + const encode = value => { + position > safeEnd && (target = makeRoom(position)); + var length, type = typeof value; + if ("string" === type) { + if (packedObjectMap) { + let packedPosition = packedObjectMap[value]; + if (packedPosition >= 0) return void (packedPosition < 16 ? target[position++] = packedPosition + 224 : (target[position++] = 198, + encode(1 & packedPosition ? 15 - packedPosition >> 1 : packedPosition - 16 >> 1))); + if (samplingPackedValues && !options.pack) { + let status = samplingPackedValues.get(value); + status ? status.count++ : samplingPackedValues.set(value, { + count: 1 + }); + } + } + let headerSize, strLength = value.length; + if (bundledStrings && strLength >= 4 && strLength < 1024) { + if ((bundledStrings.size += strLength) > 61440) { + let extStart, maxBytes = (bundledStrings[0] ? 3 * bundledStrings[0].length + bundledStrings[1].length : 0) + 10; + position + maxBytes > safeEnd && (target = makeRoom(position + maxBytes)), target[position++] = 217, + target[position++] = 223, target[position++] = 249, target[position++] = bundledStrings.position ? 132 : 130, + target[position++] = 26, extStart = position - start, position += 4, bundledStrings.position && writeBundles(start, encode), + bundledStrings = [ "", "" ], bundledStrings.size = 0, bundledStrings.position = extStart; + } + let twoByte = hasNonLatin.test(value); + return bundledStrings[twoByte ? 0 : 1] += value, target[position++] = twoByte ? 206 : 207, + void encode(strLength); + } + headerSize = strLength < 32 ? 1 : strLength < 256 ? 2 : strLength < 65536 ? 3 : 5; + let maxBytes = 3 * strLength; + if (position + maxBytes > safeEnd && (target = makeRoom(position + maxBytes)), strLength < 64 || !encodeUtf8) { + let i, c1, c2, strPosition = position + headerSize; + for (i = 0; i < strLength; i++) c1 = value.charCodeAt(i), c1 < 128 ? target[strPosition++] = c1 : c1 < 2048 ? (target[strPosition++] = c1 >> 6 | 192, + target[strPosition++] = 63 & c1 | 128) : 55296 == (64512 & c1) && 56320 == (64512 & (c2 = value.charCodeAt(i + 1))) ? (c1 = 65536 + ((1023 & c1) << 10) + (1023 & c2), + i++, target[strPosition++] = c1 >> 18 | 240, target[strPosition++] = c1 >> 12 & 63 | 128, + target[strPosition++] = c1 >> 6 & 63 | 128, target[strPosition++] = 63 & c1 | 128) : (target[strPosition++] = c1 >> 12 | 224, + target[strPosition++] = c1 >> 6 & 63 | 128, target[strPosition++] = 63 & c1 | 128); + length = strPosition - position - headerSize; + } else length = encodeUtf8(value, position + headerSize, maxBytes); + length < 24 ? target[position++] = 96 | length : length < 256 ? (headerSize < 2 && target.copyWithin(position + 2, position + 1, position + 1 + length), + target[position++] = 120, target[position++] = length) : length < 65536 ? (headerSize < 3 && target.copyWithin(position + 3, position + 2, position + 2 + length), + target[position++] = 121, target[position++] = length >> 8, target[position++] = 255 & length) : (headerSize < 5 && target.copyWithin(position + 5, position + 3, position + 3 + length), + target[position++] = 122, targetView.setUint32(position, length), position += 4), + position += length; + } else if ("number" === type) if (this.alwaysUseFloat || value >>> 0 !== value) if (this.alwaysUseFloat || value >> 0 !== value) { + let useFloat32; + if ((useFloat32 = this.useFloat32) > 0 && value < 4294967296 && value >= -2147483648) { + let xShifted; + if (target[position++] = 250, targetView.setFloat32(position, value), useFloat32 < 4 || (xShifted = value * mult10[(127 & target[position]) << 1 | target[position + 1] >> 7]) >> 0 === xShifted) return void (position += 4); + position--; + } + target[position++] = 251, targetView.setFloat64(position, value), position += 8; + } else value >= -24 ? target[position++] = 31 - value : value >= -256 ? (target[position++] = 56, + target[position++] = ~value) : value >= -65536 ? (target[position++] = 57, targetView.setUint16(position, ~value), + position += 2) : (target[position++] = 58, targetView.setUint32(position, ~value), + position += 4); else value < 24 ? target[position++] = value : value < 256 ? (target[position++] = 24, + target[position++] = value) : value < 65536 ? (target[position++] = 25, target[position++] = value >> 8, + target[position++] = 255 & value) : (target[position++] = 26, targetView.setUint32(position, value), + position += 4); else if ("object" === type) if (value) { + if (referenceMap) { + let referee = referenceMap.get(value); + if (referee) { + if (target[position++] = 216, target[position++] = 29, target[position++] = 25, + !referee.references) { + let idsToInsert = referenceMap.idsToInsert || (referenceMap.idsToInsert = []); + referee.references = [], idsToInsert.push(referee); + } + return referee.references.push(position - start), void (position += 2); + } + referenceMap.set(value, { + offset: position - start + }); + } + let constructor = value.constructor; + if (constructor === Object) writeObject(value, !0); else if (constructor === Array) { + (length = value.length) < 24 ? target[position++] = 128 | length : writeArrayHeader(length); + for (let i = 0; i < length; i++) encode(value[i]); + } else if (constructor === Map) if ((this.mapsAsObjects ? !1 !== this.useTag259ForMaps : this.useTag259ForMaps) && (target[position++] = 217, + target[position++] = 1, target[position++] = 3), (length = value.size) < 24 ? target[position++] = 160 | length : length < 256 ? (target[position++] = 184, + target[position++] = length) : length < 65536 ? (target[position++] = 185, target[position++] = length >> 8, + target[position++] = 255 & length) : (target[position++] = 186, targetView.setUint32(position, length), + position += 4), encoder.keyMap) for (let [key, entryValue] of value) encode(encoder.encodeKey(key)), + encode(entryValue); else for (let [key, entryValue] of value) encode(key), encode(entryValue); else { + for (let i = 0, l = extensions.length; i < l; i++) { + if (value instanceof extensionClasses[i]) { + let extension = extensions[i], tag = extension.tag; + return null == tag && (tag = extension.getTag && extension.getTag.call(this, value)), + tag < 24 ? target[position++] = 192 | tag : tag < 256 ? (target[position++] = 216, + target[position++] = tag) : tag < 65536 ? (target[position++] = 217, target[position++] = tag >> 8, + target[position++] = 255 & tag) : tag > -1 && (target[position++] = 218, targetView.setUint32(position, tag), + position += 4), void extension.encode.call(this, value, encode, makeRoom); + } + } + if (value[Symbol.iterator]) { + if (throwOnIterable) { + let error = new Error("Iterable should be serialized as iterator"); + throw error.iteratorNotHandled = !0, error; + } + target[position++] = 159; + for (let entry of value) encode(entry); + return void (target[position++] = 255); + } + if (value[Symbol.asyncIterator] || isBlob(value)) { + let error = new Error("Iterable/blob should be serialized as iterator"); + throw error.iteratorNotHandled = !0, error; + } + if (this.useToJSON && value.toJSON) { + const json = value.toJSON(); + if (json !== value) return encode(json); + } + writeObject(value, !value.hasOwnProperty); + } + } else target[position++] = 246; else if ("boolean" === type) target[position++] = value ? 245 : 244; else if ("bigint" === type) { + if (value < BigInt(1) << BigInt(64) && value >= 0) target[position++] = 27, targetView.setBigUint64(position, value); else if (value > -(BigInt(1) << BigInt(64)) && value < 0) target[position++] = 59, + targetView.setBigUint64(position, -value - BigInt(1)); else { + if (!this.largeBigIntToFloat) throw new RangeError(value + " was too large to fit in CBOR 64-bit integer format, set largeBigIntToFloat to convert to float-64"); + target[position++] = 251, targetView.setFloat64(position, Number(value)); + } + position += 8; + } else { + if ("undefined" !== type) throw new Error("Unknown type: " + type); + target[position++] = 247; + } + }, writeObject = !1 === this.useRecords ? this.variableMapSize ? object => { + let keys = Object.keys(object), vals = Object.values(object), length = keys.length; + if (length < 24 ? target[position++] = 160 | length : length < 256 ? (target[position++] = 184, + target[position++] = length) : length < 65536 ? (target[position++] = 185, target[position++] = length >> 8, + target[position++] = 255 & length) : (target[position++] = 186, targetView.setUint32(position, length), + position += 4), encoder.keyMap) for (let i = 0; i < length; i++) encode(encoder.encodeKey(keys[i])), + encode(vals[i]); else for (let i = 0; i < length; i++) encode(keys[i]), encode(vals[i]); + } : (object, safePrototype) => { + target[position++] = 185; + let objectOffset = position - start; + position += 2; + let size = 0; + if (encoder.keyMap) for (let key in object) (safePrototype || object.hasOwnProperty(key)) && (encode(encoder.encodeKey(key)), + encode(object[key]), size++); else for (let key in object) (safePrototype || object.hasOwnProperty(key)) && (encode(key), + encode(object[key]), size++); + target[objectOffset++ + start] = size >> 8, target[objectOffset + start] = 255 & size; + } : (object, safePrototype) => { + let nextTransition, parentRecordId, keys, transition = structures.transitions || (structures.transitions = Object.create(null)), newTransitions = 0, length = 0; + if (this.keyMap) { + keys = Object.keys(object).map((k => this.encodeKey(k))), length = keys.length; + for (let i = 0; i < length; i++) { + let key = keys[i]; + nextTransition = transition[key], nextTransition || (nextTransition = transition[key] = Object.create(null), + newTransitions++), transition = nextTransition; + } + } else for (let key in object) (safePrototype || object.hasOwnProperty(key)) && (nextTransition = transition[key], + nextTransition || (1048576 & transition[RECORD_SYMBOL] && (parentRecordId = 65535 & transition[RECORD_SYMBOL]), + nextTransition = transition[key] = Object.create(null), newTransitions++), transition = nextTransition, + length++); + let recordId = transition[RECORD_SYMBOL]; + if (void 0 !== recordId) recordId &= 65535, target[position++] = 217, target[position++] = recordId >> 8 | 224, + target[position++] = 255 & recordId; else { + if (keys || (keys = transition.__keys__ || (transition.__keys__ = Object.keys(object))), + void 0 === parentRecordId ? (recordId = structures.nextId++, recordId || (recordId = 0, + structures.nextId = 1), recordId >= 256 && (structures.nextId = (recordId = maxSharedStructures) + 1)) : recordId = parentRecordId, + structures[recordId] = keys, !(recordId < maxSharedStructures)) { + if (transition[RECORD_SYMBOL] = recordId, targetView.setUint32(position, 3655335680), + position += 3, newTransitions && (transitionsCount += serializationsSinceTransitionRebuild * newTransitions), + recordIdsToRemove.length >= 256 - maxSharedStructures && (recordIdsToRemove.shift()[RECORD_SYMBOL] = void 0), + recordIdsToRemove.push(transition), writeArrayHeader(length + 2), encode(57344 + recordId), + encode(keys), null === safePrototype) return; + for (let key in object) (safePrototype || object.hasOwnProperty(key)) && encode(object[key]); + return; + } + target[position++] = 217, target[position++] = recordId >> 8 | 224, target[position++] = 255 & recordId, + transition = structures.transitions; + for (let i = 0; i < length; i++) (void 0 === transition[RECORD_SYMBOL] || 1048576 & transition[RECORD_SYMBOL]) && (transition[RECORD_SYMBOL] = recordId), + transition = transition[keys[i]]; + transition[RECORD_SYMBOL] = 1048576 | recordId, hasSharedUpdate = !0; + } + if (length < 24 ? target[position++] = 128 | length : writeArrayHeader(length), + null !== safePrototype) for (let key in object) (safePrototype || object.hasOwnProperty(key)) && encode(object[key]); + }, makeRoom = end => { + let newSize; + if (end > 16777216) { + if (end - start > MAX_BUFFER_SIZE) throw new Error("Encoded buffer would be larger than maximum buffer size"); + newSize = Math.min(MAX_BUFFER_SIZE, 4096 * Math.round(Math.max((end - start) * (end > 67108864 ? 1.25 : 2), 4194304) / 4096)); + } else newSize = 1 + (Math.max(end - start << 2, target.length - 1) >> 12) << 12; + let newBuffer = new ByteArrayAllocate(newSize); + return targetView = new DataView(newBuffer.buffer, 0, newSize), target.copy ? target.copy(newBuffer, 0, start, end) : newBuffer.set(target.slice(start, end)), + position -= start, start = 0, safeEnd = newBuffer.length - 10, target = newBuffer; + }; + let chunkThreshold = 100, continuedChunkThreshold = 1e3; + function* encodeObjectAsIterable(object, iterateProperties, finalIterable) { + let constructor = object.constructor; + if (constructor === Object) { + let useRecords = !1 !== encoder.useRecords; + useRecords ? writeObject(object, null) : writeEntityLength(Object.keys(object).length, 160); + for (let key in object) { + let value = object[key]; + useRecords || encode(key), value && "object" == typeof value ? iterateProperties[key] ? yield* encodeObjectAsIterable(value, iterateProperties[key]) : yield* tryEncode(value, iterateProperties, key) : encode(value); + } + } else if (constructor === Array) { + let length = object.length; + writeArrayHeader(length); + for (let i = 0; i < length; i++) { + let value = object[i]; + value && ("object" == typeof value || position - start > chunkThreshold) ? iterateProperties.element ? yield* encodeObjectAsIterable(value, iterateProperties.element) : yield* tryEncode(value, iterateProperties, "element") : encode(value); + } + } else if (object[Symbol.iterator]) { + target[position++] = 159; + for (let value of object) value && ("object" == typeof value || position - start > chunkThreshold) ? iterateProperties.element ? yield* encodeObjectAsIterable(value, iterateProperties.element) : yield* tryEncode(value, iterateProperties, "element") : encode(value); + target[position++] = 255; + } else isBlob(object) ? (writeEntityLength(object.size, 64), yield target.subarray(start, position), + yield object, restartEncoding()) : object[Symbol.asyncIterator] ? (target[position++] = 159, + yield target.subarray(start, position), yield object, restartEncoding(), target[position++] = 255) : encode(object); + finalIterable && position > start ? yield target.subarray(start, position) : position - start > chunkThreshold && (yield target.subarray(start, position), + restartEncoding()); + } + function* tryEncode(value, iterateProperties, key) { + let restart = position - start; + try { + encode(value), position - start > chunkThreshold && (yield target.subarray(start, position), + restartEncoding()); + } catch (error) { + if (!error.iteratorNotHandled) throw error; + iterateProperties[key] = {}, position = start + restart, yield* encodeObjectAsIterable.call(this, value, iterateProperties[key]); + } + } + function restartEncoding() { + chunkThreshold = continuedChunkThreshold, encoder.encode(null, THROW_ON_ITERABLE); + } + function startEncoding(value, options, encodeIterable) { + return chunkThreshold = options && options.chunkThreshold ? continuedChunkThreshold = options.chunkThreshold : 100, + value && "object" == typeof value ? (encoder.encode(null, THROW_ON_ITERABLE), encodeIterable(value, encoder.iterateProperties || (encoder.iterateProperties = {}), !0)) : [ encoder.encode(value) ]; + } + async function* encodeObjectAsAsyncIterable(value, iterateProperties) { + for (let encodedValue of encodeObjectAsIterable(value, iterateProperties, !0)) { + let constructor = encodedValue.constructor; + if (constructor === ByteArray || constructor === Uint8Array) yield encodedValue; else if (isBlob(encodedValue)) { + let next, reader = encodedValue.stream().getReader(); + for (;!(next = await reader.read()).done; ) yield next.value; + } else if (encodedValue[Symbol.asyncIterator]) for await (let asyncValue of encodedValue) restartEncoding(), + asyncValue ? yield* encodeObjectAsAsyncIterable(asyncValue, iterateProperties.async || (iterateProperties.async = {})) : yield encoder.encode(asyncValue); else yield encodedValue; + } + } + this.encodeAsIterable = function(value, options) { + return startEncoding(value, options, encodeObjectAsIterable); + }, this.encodeAsAsyncIterable = function(value, options) { + return startEncoding(value, options, encodeObjectAsAsyncIterable); + }; + } + useBuffer(buffer) { + target = buffer, targetView = new DataView(target.buffer, target.byteOffset, target.byteLength), + position = 0; + } + clearSharedData() { + this.structures && (this.structures = []), this.sharedValues && (this.sharedValues = void 0); + } + updateSharedData() { + let lastVersion = this.sharedVersion || 0; + this.sharedVersion = lastVersion + 1; + let structuresCopy = this.structures.slice(0), sharedData = new SharedData(structuresCopy, this.sharedValues, this.sharedVersion), saveResults = this.saveShared(sharedData, (existingShared => (existingShared && existingShared.version || 0) == lastVersion)); + return !1 === saveResults ? (sharedData = this.getShared() || {}, this.structures = sharedData.structures || [], + this.sharedValues = sharedData.packedValues, this.sharedVersion = sharedData.version, + this.structures.nextId = this.structures.length) : structuresCopy.forEach(((structure, i) => this.structures[i] = structure)), + saveResults; + } +} + +function writeEntityLength(length, majorValue) { + length < 24 ? target[position++] = majorValue | length : length < 256 ? (target[position++] = 24 | majorValue, + target[position++] = length) : length < 65536 ? (target[position++] = 25 | majorValue, + target[position++] = length >> 8, target[position++] = 255 & length) : (target[position++] = 26 | majorValue, + targetView.setUint32(position, length), position += 4); +} + +class SharedData { + constructor(structures, values, version) { + this.structures = structures, this.packedValues = values, this.version = version; + } +} + +function writeArrayHeader(length) { + length < 24 ? target[position++] = 128 | length : length < 256 ? (target[position++] = 152, + target[position++] = length) : length < 65536 ? (target[position++] = 153, target[position++] = length >> 8, + target[position++] = 255 & length) : (target[position++] = 154, targetView.setUint32(position, length), + position += 4); +} + +const BlobConstructor = "undefined" == typeof Blob ? function() {} : Blob; + +function isBlob(object) { + if (object instanceof BlobConstructor) return !0; + let tag = object[Symbol.toStringTag]; + return "Blob" === tag || "File" === tag; +} + +function findRepetitiveStrings(value, packedValues) { + switch (typeof value) { + case "string": + if (value.length > 3) { + if (packedValues.objectMap[value] > -1 || packedValues.values.length >= packedValues.maxValues) return; + let packedStatus = packedValues.get(value); + if (packedStatus) 2 == ++packedStatus.count && packedValues.values.push(value); else if (packedValues.set(value, { + count: 1 + }), packedValues.samplingPackedValues) { + let status = packedValues.samplingPackedValues.get(value); + status ? status.count++ : packedValues.samplingPackedValues.set(value, { + count: 1 + }); + } + } + break; + + case "object": + if (value) if (value instanceof Array) for (let i = 0, l = value.length; i < l; i++) findRepetitiveStrings(value[i], packedValues); else { + let includeKeys = !packedValues.encoder.useRecords; + for (var key in value) value.hasOwnProperty(key) && (includeKeys && findRepetitiveStrings(key, packedValues), + findRepetitiveStrings(value[key], packedValues)); + } + break; + + case "function": + console.log(value); + } +} + +const isLittleEndianMachine = 1 == new Uint8Array(new Uint16Array([ 1 ]).buffer)[0]; + +function typedArrayEncoder(tag, size) { + return !isLittleEndianMachine && size > 1 && (tag -= 4), { + tag, + encode: function(typedArray, encode) { + let length = typedArray.byteLength, offset = typedArray.byteOffset || 0, buffer = typedArray.buffer || typedArray; + encode(hasNodeBuffer ? Buffer$1.from(buffer, offset, length) : new Uint8Array(buffer, offset, length)); + } + }; +} + +function writeBuffer(buffer, makeRoom) { + let length = buffer.byteLength; + length < 24 ? target[position++] = 64 + length : length < 256 ? (target[position++] = 88, + target[position++] = length) : length < 65536 ? (target[position++] = 89, target[position++] = length >> 8, + target[position++] = 255 & length) : (target[position++] = 90, targetView.setUint32(position, length), + position += 4), position + length >= target.length && makeRoom(position + length), + target.set(buffer.buffer ? buffer : new Uint8Array(buffer), position), position += length; +} + +function writeBundles(start, encode) { + targetView.setUint32(bundledStrings.position + start, position - bundledStrings.position - start + 1); + let writeStrings = bundledStrings; + bundledStrings = null, encode(writeStrings[0]), encode(writeStrings[1]); +} + +function addExtension(extension) { + if (extension.Class) { + if (!extension.encode) throw new Error("Extension has no encode function"); + extensionClasses.unshift(extension.Class), extensions.unshift(extension); + } + !function(extension) { + currentExtensions[extension.tag] = extension.decode; + }(extension); +} + +extensionClasses = [ Date, Set, Error, RegExp, Tag, ArrayBuffer, Uint8Array, Uint8ClampedArray, Uint16Array, Uint32Array, "undefined" == typeof BigUint64Array ? function() {} : BigUint64Array, Int8Array, Int16Array, Int32Array, "undefined" == typeof BigInt64Array ? function() {} : BigInt64Array, Float32Array, Float64Array, SharedData ], +extensions = [ { + tag: 1, + encode(date, encode) { + let seconds = date.getTime() / 1e3; + (this.useTimestamp32 || 0 === date.getMilliseconds()) && seconds >= 0 && seconds < 4294967296 ? (target[position++] = 26, + targetView.setUint32(position, seconds), position += 4) : (target[position++] = 251, + targetView.setFloat64(position, seconds), position += 8); + } +}, { + tag: 258, + encode(set, encode) { + encode(Array.from(set)); + } +}, { + tag: 27, + encode(error, encode) { + encode([ error.name, error.message ]); + } +}, { + tag: 27, + encode(regex, encode) { + encode([ "RegExp", regex.source, regex.flags ]); + } +}, { + getTag: tag => tag.tag, + encode(tag, encode) { + encode(tag.value); + } +}, { + encode(arrayBuffer, encode, makeRoom) { + writeBuffer(arrayBuffer, makeRoom); + } +}, { + getTag(typedArray) { + if (typedArray.constructor === Uint8Array && (this.tagUint8Array || hasNodeBuffer && !1 !== this.tagUint8Array)) return 64; + }, + encode(typedArray, encode, makeRoom) { + writeBuffer(typedArray, makeRoom); + } +}, typedArrayEncoder(68, 1), typedArrayEncoder(69, 2), typedArrayEncoder(70, 4), typedArrayEncoder(71, 8), typedArrayEncoder(72, 1), typedArrayEncoder(77, 2), typedArrayEncoder(78, 4), typedArrayEncoder(79, 8), typedArrayEncoder(85, 4), typedArrayEncoder(86, 8), { + encode(sharedData, encode) { + let packedValues = sharedData.packedValues || [], sharedStructures = sharedData.structures || []; + if (packedValues.values.length > 0) { + target[position++] = 216, target[position++] = 51, writeArrayHeader(4); + let valuesArray = packedValues.values; + encode(valuesArray), writeArrayHeader(0), writeArrayHeader(0), packedObjectMap = Object.create(sharedPackedObjectMap || null); + for (let i = 0, l = valuesArray.length; i < l; i++) packedObjectMap[valuesArray[i]] = i; + } + if (sharedStructures) { + targetView.setUint32(position, 3655335424), position += 3; + let definitions = sharedStructures.slice(0); + definitions.unshift(57344), definitions.push(new Tag(sharedData.version, 1399353956)), + encode(definitions); + } else encode(new Tag(sharedData.version, 1399353956)); + } +} ]; + +let defaultEncoder = new Encoder({ + useRecords: !1 +}); + +const encode = defaultEncoder.encode, encodeAsIterable = defaultEncoder.encodeAsIterable, encodeAsAsyncIterable = defaultEncoder.encodeAsAsyncIterable, {NEVER, ALWAYS, DECIMAL_ROUND, DECIMAL_FIT} = FLOAT32_OPTIONS, REUSE_BUFFER_MODE = 512, RESET_BUFFER_MODE = 1024, THROW_ON_ITERABLE = 2048; + +function encodeIter(objectIterator, options = {}) { + if (objectIterator && "object" == typeof objectIterator) { + if ("function" == typeof objectIterator[Symbol.iterator]) return function*(objectIterator, options) { + const encoder = new Encoder(options); + for (const value of objectIterator) yield encoder.encode(value); + }(objectIterator, options); + if ("function" == typeof objectIterator.then || "function" == typeof objectIterator[Symbol.asyncIterator]) return async function*(objectIterator, options) { + const encoder = new Encoder(options); + for await (const value of objectIterator) yield encoder.encode(value); + }(objectIterator, options); + throw new Error("first argument must be an Iterable, Async Iterable, Iterator, Async Iterator, or a Promise"); + } + throw new Error("first argument must be an Iterable, Async Iterable, or a Promise for an Async Iterable"); +} + +function decodeIter(bufferIterator, options = {}) { + if (!bufferIterator || "object" != typeof bufferIterator) throw new Error("first argument must be an Iterable, Async Iterable, Iterator, Async Iterator, or a promise"); + const decoder = new Decoder(options); + let incomplete; + const parser = chunk => { + let yields; + incomplete && (chunk = Buffer.concat([ incomplete, chunk ]), incomplete = void 0); + try { + yields = decoder.decodeMultiple(chunk); + } catch (err) { + if (!err.incomplete) throw err; + incomplete = chunk.slice(err.lastPosition), yields = err.values; + } + return yields; + }; + return "function" == typeof bufferIterator[Symbol.iterator] ? function*() { + for (const value of bufferIterator) yield* parser(value); + }() : "function" == typeof bufferIterator[Symbol.asyncIterator] ? async function*() { + for await (const value of bufferIterator) yield* parser(value); + }() : void 0; +} + +export { ALWAYS, DECIMAL_FIT, DECIMAL_ROUND, Decoder, Encoder, FLOAT32_OPTIONS, NEVER, REUSE_BUFFER_MODE, Tag, addExtension, clearSource, decode, decodeIter, decodeMultiple, encode, encodeAsAsyncIterable, encodeAsIterable, encodeIter, isNativeAccelerationEnabled, roundFloat32 }; diff --git a/delivery/common/cwt/examples/cwt-ps256/cwt.js b/delivery/common/cwt/examples/cwt-ps256/cwt.js new file mode 100644 index 00000000..a207206b --- /dev/null +++ b/delivery/common/cwt/examples/cwt-ps256/cwt.js @@ -0,0 +1,360 @@ +/** @preserve @version 1.2.0 */ +import { crypto } from "crypto"; + +import { Encoder, Decoder, Tag } from "./cbor-x.js"; + +import { logger } from "log"; + +const COSE_Mac0 = 17, COSE_Mac = 97, COSE_Sign = 98, COSE_Sign1 = 18, COSE_Encrypt0 = 16, COSE_Encrypt = 96, coseAlgTags = { + "-7": "ES256", + 5: "HS256", + "-37": "PS256" +}, HeaderLabels = { + alg: 1, + crit: 2, + kid: 4 +}, ClaimLabels = { + iss: 1, + sub: 2, + aud: 3, + exp: 4, + nbf: 5, + iat: 6, + cti: 7 +}, AlgorithmLabels = { + ES256: -7, + HS256: 5, + PS256: -37 +}; + +class Mac { + static async verifyHMAC(alg, message, signature, key) { + if ("HS256" === alg) return await crypto.subtle.verify({ + name: "HMAC" + }, key, signature, message); + throw new Error(`Unsupported Algorithm, ${alg}`); + } + static async doSign(signaturePayload, key, alg) { + if ("HS256" === alg) { + return await crypto.subtle.sign({ + name: "HMAC", + hash: { + name: "SHA-256" + } + }, key, signaturePayload); + } + throw new Error(`Unsupported Algorithm, ${alg}`); + } +} + +class CWTUtil { + static toHexString(byteArray) { + return Array.prototype.map.call(byteArray, (function(byte) { + return ("0" + (255 & byte).toString(16)).slice(-2); + })).join(""); + } + static claimsTranslate(payload, labelsMap, translators) { + if (payload instanceof Map) { + const result = new Map; + for (const [k, v] of payload) { + const tK = labelsMap[k] ? labelsMap[k] : k, tV = translators && translators[tK] ? translators[tK](v) : v; + result.set(tK, tV); + } + return result; + } + { + const result = new Map; + for (const param in payload) { + const key = labelsMap[param] ? labelsMap[param] : param, theValue = translators && translators[key] ? translators[key](payload[param]) : payload[param]; + result.set(key, theValue); + } + return result; + } + } + static isUint8ArrayEqual(arr1, arr2) { + return arr1 instanceof Uint8Array && (arr2 instanceof Uint8Array && (arr1.length === arr2.length && arr1.every(((value, index) => value === arr2[index])))); + } +} + +CWTUtil.EMPTY_BUFFER = new Uint8Array(0); + +class Sign { + static async verifySignature(alg, message, signature, key) { + switch (alg) { + case "ES256": + return await crypto.subtle.verify({ + name: "ECDSA", + hash: "SHA-256" + }, key, signature, new Uint8Array(message)); + + case "PS256": + return await crypto.subtle.verify({ + name: "RSA-PSS", + saltLength: 32 + }, key, signature, new Uint8Array(message)); + + default: + throw new Error(`Unsupported Algorithm, ${alg}`); + } + } + static async doSign(signaturePayload, key, alg) { + switch (alg) { + case "ES256": + return await crypto.subtle.sign({ + name: "ECDSA", + hash: { + name: "SHA-256" + } + }, key, signaturePayload); + + case "PS256": + return await crypto.subtle.sign({ + name: "RSA-PSS", + saltLength: 32 + }, key, signaturePayload); + + default: + throw new Error(`Unsupported Algorithm, ${alg}`); + } + } +} + +globalThis.cborenc = new Encoder({ + tagUint8Array: !1 +}), globalThis.cbordec = new Decoder; + +class CWTValidator { + constructor(cwtOptions) { + this.cwtOptions = cwtOptions || {}, this.validateOptionTypes(); + } + async validate(tokenBuf, verifiers) { + if (!(tokenBuf instanceof Uint8Array)) throw new Error("Invalid token type, expected Uint8Array!"); + if (verifiers.find((elem => void 0 === elem.key || void 0 === elem.key.type || null == elem.key.algorithm || null == elem.key.usages || elem.externalAAD && !(elem.externalAAD instanceof Uint8Array) || elem.kid && !(elem.kid instanceof Uint8Array)))) throw new Error("Invalid verifiers, expected list of verifier with valid crypto key!"); + let coseMessage = globalThis.cbordec.decode(tokenBuf), cwtType = this.cwtOptions.defaultCoseMsgType; + if (this.cwtOptions.isCWTTagAdded) { + if (61 !== coseMessage.tag) throw new Error("CWT malformed: expected CWT CBOR tag for the token!"); + coseMessage = coseMessage.value; + } + if (this.cwtOptions.isCoseCborTagAdded) { + if (cwtType = coseMessage.tag, ![ COSE_Mac0, COSE_Mac, COSE_Sign, COSE_Sign1, COSE_Encrypt, COSE_Encrypt0 ].includes(cwtType)) throw new Error("CWT malformed: invalid COSE CBOR tag!"); + coseMessage = coseMessage.value; + } + const cwtJSON = await this.verifyCoseMessage(coseMessage, cwtType, verifiers, this.cwtOptions.headerValidation); + return this.validateClaims(cwtJSON.payload), cwtJSON; + } + async verifyCoseMessage(coseMessage, cwtType, verifiers, headerValidation) { + switch (cwtType) { + case COSE_Mac0: + { + if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR MAC0Tag, expected array of length 4!"); + const [p, u, payload, tag] = coseMessage; + let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; + const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; + let alg; + if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabels.alg); else { + if (uH === CWTUtil.EMPTY_BUFFER) throw new Error("CWT malformed: unable to find algo field from CWT token."); + alg = uH.get(HeaderLabels.alg); + } + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER; + alg = coseAlgTags[alg]; + let isSignVerified = !1; + for (const verifier of verifiers) { + const MACstructure = [ "MAC0", pP, verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, payload ], toBeVerified = globalThis.cborenc.encode(MACstructure); + if (isSignVerified = await Mac.verifyHMAC(alg, toBeVerified, tag, verifier.key), + isSignVerified) break; + } + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); + const decodedPayload = globalThis.cbordec.decode(payload); + return Promise.resolve({ + header: { + p: pH, + u: uH + }, + payload: decodedPayload + }); + } + + case COSE_Sign1: + { + if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR COSE_Sign1, expected array of length 4!"); + const [p, u, payload, signature] = coseMessage; + let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; + pH = pH.size ? pH : CWTUtil.EMPTY_BUFFER; + const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; + let alg; + if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabels.alg); else { + if (uH === CWTUtil.EMPTY_BUFFER) throw new Error("CWT malformed: unable to find algo field from CWT token."); + alg = u.get(HeaderLabels.alg); + } + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER; + alg = coseAlgTags[alg]; + let isSignVerified = !1; + for (const verifier of verifiers) { + const SigStructure = [ "Signature1", pP, verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, payload ], toBeVerified = globalThis.cborenc.encode(SigStructure); + if (isSignVerified = await Sign.verifySignature(alg, toBeVerified, signature, verifier.key), + isSignVerified) break; + } + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); + const decodedPayload = globalThis.cbordec.decode(payload); + return Promise.resolve({ + header: { + p: pH, + u: uH + }, + payload: decodedPayload + }); + } + + case COSE_Sign: + { + if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR COSE_Sign, expected array of length 4!"); + const [p, u, payload, signatures] = coseMessage; + let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; + const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; + headerValidation && this.validateHeader(pH, !0); + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER, isSignVerified = !1; + for (const signature of signatures) { + let [signP, signU, sign] = signature; + const verifier = this.getVerifier(signU, verifiers); + if (verifier) { + const externalAAD = verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, signerPMap = signP.length ? globalThis.cborenc.decode(signP) : CWTUtil.EMPTY_BUFFER; + signP = signerPMap.size ? signP : CWTUtil.EMPTY_BUFFER; + const alg = signerPMap.get ? signerPMap.get(HeaderLabels.alg) : pH.get ? pH.get(HeaderLabels.alg) : void 0; + if (alg) { + const SigStructure = [ "Signature", pP, signP, externalAAD, payload ], toBeVerified = globalThis.cborenc.encode(SigStructure); + if (isSignVerified = await Sign.verifySignature(coseAlgTags[alg], toBeVerified, sign, verifier.key), + isSignVerified) break; + } else logger.error(`Unable to find alg in CWT token for kid = ${verifier.kid}, hence skipping the verifier!`); + } + } + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); + const decodedPayload = globalThis.cbordec.decode(payload); + return Promise.resolve({ + header: { + p: pH, + u: uH + }, + payload: decodedPayload + }); + } + + default: + throw new Error(`COSE CBOR tag ${cwtType} is not supported at the moment`); + } + } + validateHeader(headers, pheader) { + if (headers.size && pheader) { + const h = headers, crit = h.get(HeaderLabels.crit); + if (Array.isArray(crit)) { + if (0 === crit.length) throw new Error("CWT Malformed: malformed protected header, crit array cannot be empty!"); + for (const e of crit) if (void 0 === h.get(e)) throw new Error("CWT Malformed: malformed protected header, crit labels are not part of protected header"); + } + } + } + validateClaims(decodedPayload) { + if (this.cwtOptions.issuer) { + const iss = decodedPayload.get(ClaimLabels.iss); + if (iss && this.cwtOptions.issuer !== iss) throw new Error(`CWT malformed: invalid iss, expected ${this.cwtOptions.issuer}`); + } + if (this.cwtOptions.subject) { + const sub = decodedPayload.get(ClaimLabels.sub); + if (sub && this.cwtOptions.subject !== sub) throw new Error(`CWT malformed: invalid sub, expected ${this.cwtOptions.subject}`); + } + if (this.cwtOptions.audience) { + const aud = decodedPayload.get(ClaimLabels.aud); + if (aud && (Array.isArray(aud) || "string" == typeof aud)) { + if (!(Array.isArray(aud) ? aud : [ aud ]).includes(this.cwtOptions.audience)) throw new Error(`CWT malformed: invalid aud, expected ${this.cwtOptions.audience}`); + } + } + const clockTimestamp = Math.floor(Date.now() / 1e3); + if (!1 === this.cwtOptions.ignoreExpiration) { + const exp = decodedPayload.get(ClaimLabels.exp); + if (exp) { + if ("number" != typeof exp) throw new Error("CWT malformed: exp must be number"); + if (clockTimestamp > exp + (this.cwtOptions.clockTolerance || 0)) throw new Error("CWT token has been expired"); + } + } + if (!1 === this.cwtOptions.ignoreNotBefore) { + const nbf = decodedPayload.get(ClaimLabels.nbf); + if (nbf) { + if ("number" != typeof nbf) throw new Error("CWT malformed: nbf must be number"); + if (nbf > clockTimestamp + (this.cwtOptions.clockTolerance || 0)) throw new Error("CWT is not active"); + } + } + } + validateOptionTypes() { + if (void 0 === this.cwtOptions.isCWTTagAdded) this.cwtOptions.isCWTTagAdded = !1; else if ("boolean" != typeof this.cwtOptions.isCWTTagAdded) throw new Error("Invalid cwtOptions: isCWTTagAdded must be boolean"); + if (null == this.cwtOptions.isCoseCborTagAdded) this.cwtOptions.isCoseCborTagAdded = !0; else if ("boolean" != typeof this.cwtOptions.isCoseCborTagAdded) throw new Error("Invalid cwtOptions: isCoseCborTagAdded must be boolean"); + if (void 0 === this.cwtOptions.defaultCoseMsgType) this.cwtOptions.defaultCoseMsgType = COSE_Mac0; else if ("number" != typeof this.cwtOptions.defaultCoseMsgType) throw new Error("Invalid cwtOptions: defaultCoseMsgType must be number"); + if (void 0 === this.cwtOptions.headerValidation) this.cwtOptions.headerValidation = !1; else if ("boolean" != typeof this.cwtOptions.headerValidation) throw new Error("Invalid cwtOptions: headerValidation must be boolean"); + if (void 0 !== this.cwtOptions.issuer && ("string" != typeof this.cwtOptions.issuer || 0 === this.cwtOptions.issuer.trim().length)) throw new Error("Invalid cwtOptions: issuer must be non empty string"); + if (void 0 !== this.cwtOptions.subject && ("string" != typeof this.cwtOptions.subject || 0 === this.cwtOptions.subject.trim().length)) throw new Error("Invalid cwtOptions: subject must be non empty string"); + if (void 0 !== this.cwtOptions.audience && !("string" == typeof this.cwtOptions.audience && this.cwtOptions.audience.trim().length > 0 || Array.isArray(this.cwtOptions.audience))) throw new Error("Invalid cwtOptions: audience must be non empty string or array"); + if (void 0 === this.cwtOptions.ignoreExpiration) this.cwtOptions.ignoreExpiration = !0; else if ("boolean" != typeof this.cwtOptions.ignoreExpiration) throw new Error("Invalid cwtOptions: ignoreExpiration must be boolean"); + if (void 0 === this.cwtOptions.ignoreNotBefore) this.cwtOptions.ignoreNotBefore = !0; else if ("boolean" != typeof this.cwtOptions.ignoreNotBefore) throw new Error("Invalid cwtOptions: ignoreNotBefore must be boolean"); + if (void 0 !== this.cwtOptions.clockTolerance && "number" != typeof this.cwtOptions.clockTolerance) throw new Error("Invalid cwtOptions: clockTolerance must be number"); + this.cwtOptions.clockTolerance = 60; + } + getVerifier(signU, verifiers) { + const kid = signU.get(HeaderLabels.kid); + for (const verifier of verifiers) if (CWTUtil.isUint8ArrayEqual(kid, verifier.kid)) return verifier; + return null; + } +} + +class CWTGenerator { + static async mac(claims, signer, contentHeader, recipients, options) { + if (!claims) throw new Error("Invalid claims type, cannot be null or undefined!"); + if (void 0 === signer.key || void 0 === signer.key.type || null == signer.key.algorithm || null == signer.key.usages || signer.externalAAD && !(signer.externalAAD instanceof Uint8Array)) throw new Error("Invalid signer, expected signer with valid crypto key!"); + if (!contentHeader) throw new Error("Invalid contentHeader type, cannot be null or undefined!"); + options = options || {}; + let pH = contentHeader && contentHeader.p ? contentHeader.p : new Map, uH = contentHeader && contentHeader.u ? contentHeader.u : new Map, protectedHeader = 0 === pH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(pH), alg = pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader!"); + let result, payload = globalThis.cborenc.encode(claims); + if (Array.isArray(recipients)) { + if (0 === recipients.length) throw new Error("No recipients found, there has to be atleast one recipients!"); + if (recipients.length > 0) throw new Error("Mac with recipients is not currently supported!"); + } else { + const MACstructure = [ "MAC0", protectedHeader, signer.externalAAD || CWTUtil.EMPTY_BUFFER, payload ]; + let signed = globalThis.cborenc.encode(MACstructure); + result = [ protectedHeader, uH, payload, await Mac.doSign(signed, signer.key, coseAlgTags[alg]) ], + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(result, COSE_Mac0) : result; + } + return result = options.isCWTTagAdded ? new Tag(result, 61) : result, globalThis.cborenc.encode(result); + } + static async sign(claims, signers, contentHeader, options) { + if (!claims) throw new Error("Invalid claims type, cannot be null or undefined!"); + if (!signers || Array.isArray(signers) && 0 == signers.length) throw new Error("Invalid signers, cannot be null or undefined, requies at atleast one signer!"); + if (Array.isArray(signers)) { + if (signers.find((elem => void 0 === elem.key || void 0 === elem.key.type || null == elem.key.algorithm || null == elem.key.usages || elem.externalAAD && !(elem.externalAAD instanceof Uint8Array)))) throw new Error("Invalid signers, expected list of signers with valid crypto key!"); + } else if (void 0 === signers.key || void 0 === signers.key.type || null == signers.key.algorithm || null == signers.key.usages || signers.externalAAD && !(signers.externalAAD instanceof Uint8Array)) throw new Error("Invalid signer, expected signer with valid crypto key!"); + options = options || {}; + let result, pH = contentHeader && contentHeader.p ? contentHeader.p : new Map, uH = contentHeader && contentHeader.u ? contentHeader.u : new Map, protectedHeader = 0 === pH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(pH), payload = globalThis.cborenc.encode(claims); + if (Array.isArray(signers)) { + let signatures = Array(); + for (const signer of signers) { + const externalAAD = signer.externalAAD || CWTUtil.EMPTY_BUFFER; + let signPH = signer.p ? signer.p : new Map, signUH = signer.u ? signer.u : new Map, alg = signPH.get(HeaderLabels.alg) || pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader or Signer object!"); + let signprotectedHeader = 0 === signPH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(signPH); + const SigStructure = [ "Signature", protectedHeader, signprotectedHeader, externalAAD, payload ]; + let signaturePayload = globalThis.cborenc.encode(SigStructure); + const sig = await Sign.doSign(signaturePayload, signer.key, coseAlgTags[alg]); + signatures.push([ signprotectedHeader, signUH, sig ]); + } + let signed = [ protectedHeader, uH, payload, signatures ]; + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(signed, COSE_Sign) : signed; + } else { + const externalAAD = signers.externalAAD || CWTUtil.EMPTY_BUFFER; + let alg = pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader!"); + const SigStructure = [ "Signature1", protectedHeader, externalAAD, payload ]; + let signaturePayload = globalThis.cborenc.encode(SigStructure); + let signed = [ protectedHeader, uH, payload, await Sign.doSign(signaturePayload, signers.key, coseAlgTags[alg]) ]; + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(signed, COSE_Sign1) : signed; + } + return result = options.isCWTTagAdded ? new Tag(result, 61) : result, globalThis.cborenc.encode(result); + } +} + +export { AlgorithmLabels, CWTGenerator, CWTUtil, CWTValidator, ClaimLabels, HeaderLabels }; diff --git a/delivery/common/cwt/examples/cwt-ps256/main.js b/delivery/common/cwt/examples/cwt-ps256/main.js new file mode 100644 index 00000000..7809e9b8 --- /dev/null +++ b/delivery/common/cwt/examples/cwt-ps256/main.js @@ -0,0 +1,121 @@ +import { logger } from 'log'; +import { CWTUtil, CWTValidator, CWTGenerator, AlgorithmLabels, ClaimLabels, HeaderLabels} from './cwt.js'; +import { crypto, pem2ab } from 'crypto'; +import { base16 } from 'encoding'; + +//Integer keys mapping for CWT payload. This mapping is application specific, However keys from 1-7 are reserved +const claimsLabelMap = { + 1: 'iss', + 2: 'sub', + 3: 'aud', + 4: 'exp', + 5: 'nbf', + 6: 'iat' +}; + +//advanced options for cwt validator +const cwtOptions = { + //perform header validation + headerValidation: false, + //check token expiry + ignoreExpiration: true, + //check token nbf + ignoreNotBefore: true +}; + +export const ps256PrivKey1 = `-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKj +MzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvu +NMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZ +qgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulg +p2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlR +ZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwi +VuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskV +laAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8 +sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83H +mQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwY +dgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cw +ta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQ +DM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2T +N0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t +0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPv +t8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDU +AhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk +48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISL +DY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnK +xt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEA +mNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh +2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfz +et6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhr +VBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicD +TQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cnc +dn/RsYEONbwQSjIfMPkvxF+8HQ== +-----END PRIVATE KEY-----`; + +export const ps256PubKey1 = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo +4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u ++qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh +kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ +0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg +cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc +mwIDAQAB +-----END PUBLIC KEY-----`; + + +const cwtValidator = new CWTValidator(cwtOptions); + +export async function onClientRequest (request) { + + try { + const signingKey = await crypto.subtle.importKey( + 'pkcs8', + pem2ab(ps256PrivKey1), + { + name: 'RSA-PSS', + hash: 'SHA-256' + }, + !1, + ['sign'] + ); + if (request.path == '/token' && request.method == 'POST') { + const claims = { iss: 'mde_dev@akamai.com', iat: Date.now(), sub: "subject@akamai.com", aud: ["cdn@akamai.com"], exp: Date.now() + 86400, nbf: Date.now() - 86400}; + const claimsSet = CWTUtil.claimsTranslate(claims, ClaimLabels); + const signer = { + key: signingKey + }; + const cwtToken = await CWTGenerator.sign(claimsSet, signer, { p: CWTUtil.claimsTranslate({ alg: AlgorithmLabels.PS256 }, HeaderLabels)}); + const cwtTokenHex = base16.encode(new Uint8Array(cwtToken)); + request.respondWith(200, {}, cwtTokenHex); + } else { + const verifyKey = await crypto.subtle.importKey( + 'spki', + pem2ab(ps256PubKey1), + { + name: 'RSA-PSS', + hash: 'SHA-256' + }, + false, + ['verify'] + ); + //Fetch the Authorization header from request + let cwtToken = request.getHeader('Authorization'); + if (cwtToken){ + cwtToken = cwtToken[0]; + //replace auth scheme before validating + cwtToken = cwtToken.replace('Bearer ',''); + //Assumption: CWT token as passed as hex encoded in authorization header. We decode the hex to get the binary + const tokenBuf = base16.decode(cwtToken,'Uint8Array'); + const cwtJSON = await cwtValidator.validate(tokenBuf,[{key: verifyKey}]); + const claims = CWTUtil.claimsTranslate(cwtJSON.payload,claimsLabelMap); + request.respondWith(200, {}, JSON.stringify(Object.fromEntries(claims))); + } else { + //Return bad request of authorization header is not found + request.respondWith(400, {}, 'Authorization header is missing!'); + } + } + } catch (error) { + logger.log(error); + request.respondWith(400, {}, error.message); + } +} \ No newline at end of file diff --git a/delivery/common/cwt/examples/typescript/src/cwt/cbor-x.js b/delivery/common/cwt/examples/typescript/src/cwt/cbor-x.js index dd12806f..05f1b05e 100644 --- a/delivery/common/cwt/examples/typescript/src/cwt/cbor-x.js +++ b/delivery/common/cwt/examples/typescript/src/cwt/cbor-x.js @@ -6,12 +6,18 @@ try { let position$1 = 0; -const STOP_CODE = {}; +const RECORD_DEFINITIONS_ID = 57342, RECORD_INLINE_ID = 57343, BUNDLED_STRINGS_ID = 57337, STOP_CODE = {}; let currentStructures, srcString, bundledStrings$1, referenceMap, packedValues, dataView, restoreMapsAsObject, currentDecoder = {}, srcStringStart = 0, srcStringEnd = 0, currentExtensions = [], currentExtensionRanges = [], defaultOptions = { useRecords: !1, mapsAsObjects: !0 -}, sequentialMode = !1; +}, sequentialMode = !1, inlineObjectReadThreshold = 2; + +try { + new Function(""); +} catch (error) { + inlineObjectReadThreshold = 1 / 0; +} class Decoder { constructor(options) { @@ -201,9 +207,7 @@ function read() { return ~token; case 2: - return function(length) { - return currentDecoder.copyBuffers ? Uint8Array.prototype.slice.call(src, position$1, position$1 += length) : src.subarray(position$1, position$1 += length); - }(token); + return length = token, currentDecoder.copyBuffers ? Uint8Array.prototype.slice.call(src, position$1, position$1 += length) : src.subarray(position$1, position$1 += length); case 3: if (srcStringEnd >= position$1) return srcString.slice(position$1 - srcStringStart, (position$1 += token) - srcStringStart); @@ -240,18 +244,28 @@ function read() { } case 6: - if (token >= 57337) { + if (token >= BUNDLED_STRINGS_ID) { let structure = currentStructures[8191 & token]; if (structure) return structure.read || (structure.read = createStructureReader(structure)), structure.read(); if (token < 65536) { - if (57343 == token) return recordDefinition(read()); - if (57342 == token) { + if (token == RECORD_INLINE_ID) { + let length = readJustLength(), id = read(), structure = read(); + recordDefinition(id, structure); + let object = {}; + if (currentDecoder.keyMap) for (let i = 2; i < length; i++) { + object[safeKey(currentDecoder.decodeKey(structure[i - 2]))] = read(); + } else for (let i = 2; i < length; i++) { + object[safeKey(structure[i - 2])] = read(); + } + return object; + } + if (token == RECORD_DEFINITIONS_ID) { let length = readJustLength(), id = read(); - for (let i = 2; i < length; i++) recordDefinition([ id++, read() ]); + for (let i = 2; i < length; i++) recordDefinition(id++, read()); return read(); } - if (57337 == token) return function() { + if (token == BUNDLED_STRINGS_ID) return function() { let length = readJustLength(), bundlePosition = position$1 + read(); for (let i = 2; i < length; i++) { let bundleLength = readJustLength(); @@ -305,6 +319,7 @@ function read() { } throw new Error("Unknown CBOR token " + token); } + var length; } const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/; @@ -333,7 +348,7 @@ function createStructureReader(structure) { if (compiledReader.propertyCount === length) return compiledReader(read); compiledReader = compiledReader.next; } - if (this.slowReads++ >= 3) { + if (this.slowReads++ >= inlineObjectReadThreshold) { let array = this.length == length ? this : this.slice(0, length); return compiledReader = currentDecoder.keyMap ? new Function("r", "return {" + array.map((k => currentDecoder.decodeKey(k))).map((k => validName.test(k) ? safeKey(k) + ":r()" : "[" + JSON.stringify(k) + "]:r()")).join(",") + "}") : new Function("r", "return {" + array.map((key => validName.test(key) ? safeKey(key) + ":r()" : "[" + JSON.stringify(key) + "]:r()")).join(",") + "}"), this.compiledReader && (compiledReader.next = this.compiledReader), compiledReader.propertyCount = length, @@ -346,7 +361,9 @@ function createStructureReader(structure) { } function safeKey(key) { - return "__proto__" === key ? "__proto_" : key; + if ("string" == typeof key) return "__proto__" === key ? "__proto_" : key; + if ("object" != typeof key) return key.toString(); + throw new Error("Invalid property name type " + typeof key); } let readFixedString = readStringJS, isNativeAccelerationEnabled = !1; @@ -467,20 +484,21 @@ currentExtensions[2] = buffer => { }, currentExtensions[3] = buffer => BigInt(-1) - currentExtensions[2](buffer), currentExtensions[4] = fraction => +(fraction[1] + "e" + fraction[0]), currentExtensions[5] = fraction => fraction[1] * Math.exp(fraction[0] * Math.log(2)); -const recordDefinition = definition => { - let id = definition[0] - 57344, structure = definition[1], existingStructure = currentStructures[id]; +const recordDefinition = (id, structure) => { + let existingStructure = currentStructures[id -= 57344]; existingStructure && existingStructure.isShared && ((currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure), currentStructures[id] = structure, structure.read = createStructureReader(structure); +}; + +currentExtensions[105] = data => { + let length = data.length, structure = data[1]; + recordDefinition(data[0], structure); let object = {}; - if (currentDecoder.keyMap) for (let i = 2, l = definition.length; i < l; i++) { - object[safeKey(currentDecoder.decodeKey(structure[i - 2]))] = definition[i]; - } else for (let i = 2, l = definition.length; i < l; i++) { - object[safeKey(structure[i - 2])] = definition[i]; + for (let i = 2; i < length; i++) { + object[safeKey(structure[i - 2])] = data[i]; } return object; -}; - -currentExtensions[105] = recordDefinition, currentExtensions[14] = value => bundledStrings$1 ? bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 += value) : new Tag(value, 14), +}, currentExtensions[14] = value => bundledStrings$1 ? bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 += value) : new Tag(value, 14), currentExtensions[15] = value => bundledStrings$1 ? bundledStrings$1[1].slice(bundledStrings$1.position1, bundledStrings$1.position1 += value) : new Tag(value, 15); let glbl = { @@ -491,8 +509,15 @@ let glbl = { currentExtensions[27] = data => (glbl[data[0]] || Error)(data[1], data[2]); const packedTable = read => { - if (132 != src[position$1++]) throw new Error("Packed values structure must be followed by a 4 element array"); + if (132 != src[position$1++]) { + let error = new Error("Packed values structure must be followed by a 4 element array"); + throw src.length < position$1 && (error.incomplete = !0), error; + } let newPackedValues = read(); + if (!newPackedValues || !newPackedValues.length) { + let error = new Error("Packed values structure must be followed by a 4 element array"); + throw error.incomplete = !0, error; + } return packedValues = packedValues ? newPackedValues.concat(packedValues.slice(newPackedValues.length)) : newPackedValues, packedValues.prefixes = read(), packedValues.suffixes = read(), read(); }; @@ -515,7 +540,8 @@ packedTable.handlesRead = !0, currentExtensions[51] = packedTable, currentExtens loadShared(); } if ("number" == typeof data) return packedValues[16 + (data >= 0 ? 2 * data : -2 * data - 1)]; - throw new Error("No support for non-integer packed references yet"); + let error = new Error("No support for non-integer packed references yet"); + throw void 0 === data && (error.incomplete = !0), error; }, currentExtensions[28] = read => { referenceMap || (referenceMap = new Map, referenceMap.id = 0); let target, id = referenceMap.id++; @@ -548,15 +574,14 @@ const isLittleEndianMachine$1 = 1 == new Uint8Array(new Uint16Array([ 1 ]).buffe for (let i = 0; i < typedArrays.length; i++) registerTypedArray(typedArrays[i], typedArrayTags[i]); function registerTypedArray(TypedArray, tag) { - let dvMethod = "get" + TypedArray.name.slice(0, -5); - "function" != typeof TypedArray && (TypedArray = null); - let bytesPerElement = TypedArray.BYTES_PER_ELEMENT; + let bytesPerElement, dvMethod = "get" + TypedArray.name.slice(0, -5); + "function" == typeof TypedArray ? bytesPerElement = TypedArray.BYTES_PER_ELEMENT : TypedArray = null; for (let littleEndian = 0; littleEndian < 2; littleEndian++) { if (!littleEndian && 1 == bytesPerElement) continue; let sizeShift = 2 == bytesPerElement ? 1 : 4 == bytesPerElement ? 2 : 3; currentExtensions[littleEndian ? tag : tag - 4] = 1 == bytesPerElement || littleEndian == isLittleEndianMachine$1 ? buffer => { if (!TypedArray) throw new Error("Could not find typed array for code " + tag); - return new TypedArray(Uint8Array.prototype.slice.call(buffer, 0).buffer); + return currentDecoder.copyBuffers || 1 !== bytesPerElement && (2 !== bytesPerElement || 1 & buffer.byteOffset) && (4 !== bytesPerElement || 3 & buffer.byteOffset) && (8 !== bytesPerElement || 7 & buffer.byteOffset) ? new TypedArray(Uint8Array.prototype.slice.call(buffer, 0).buffer) : new TypedArray(buffer.buffer, buffer.byteOffset, buffer.byteLength); } : buffer => { if (!TypedArray) throw new Error("Could not find typed array for code " + tag); let dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength), elements = buffer.length >> sizeShift, ta = new TypedArray(elements), method = dv[dvMethod]; @@ -631,7 +656,7 @@ try { textEncoder = new TextEncoder; } catch (error) {} -const hasNodeBuffer = "undefined" != typeof Buffer, ByteArrayAllocate = hasNodeBuffer ? Buffer.allocUnsafeSlow : Uint8Array, ByteArray = hasNodeBuffer ? Buffer : Uint8Array, BlobConstructor = "undefined" == typeof Blob ? {} : Blob, MAX_BUFFER_SIZE = hasNodeBuffer ? 4294967296 : 2144337920; +const Buffer$1 = "object" == typeof globalThis && globalThis.Buffer, hasNodeBuffer = void 0 !== Buffer$1, ByteArrayAllocate = hasNodeBuffer ? Buffer$1.allocUnsafeSlow : Uint8Array, ByteArray = hasNodeBuffer ? Buffer$1 : Uint8Array, MAX_BUFFER_SIZE = hasNodeBuffer ? 4294967296 : 2144337920; let throwOnIterable, target, targetView, safeEnd, position = 0, bundledStrings = null; @@ -804,13 +829,7 @@ class Encoder extends Decoder { target[position++] = 121, target[position++] = length >> 8, target[position++] = 255 & length) : (headerSize < 5 && target.copyWithin(position + 5, position + 3, position + 3 + length), target[position++] = 122, targetView.setUint32(position, length), position += 4), position += length; - } else if ("number" === type) if (value >>> 0 === value) value < 24 ? target[position++] = value : value < 256 ? (target[position++] = 24, - target[position++] = value) : value < 65536 ? (target[position++] = 25, target[position++] = value >> 8, - target[position++] = 255 & value) : (target[position++] = 26, targetView.setUint32(position, value), - position += 4); else if (value >> 0 === value) value >= -24 ? target[position++] = 31 - value : value >= -256 ? (target[position++] = 56, - target[position++] = ~value) : value >= -65536 ? (target[position++] = 57, targetView.setUint16(position, ~value), - position += 2) : (target[position++] = 58, targetView.setUint32(position, ~value), - position += 4); else { + } else if ("number" === type) if (this.alwaysUseFloat || value >>> 0 !== value) if (this.alwaysUseFloat || value >> 0 !== value) { let useFloat32; if ((useFloat32 = this.useFloat32) > 0 && value < 4294967296 && value >= -2147483648) { let xShifted; @@ -818,7 +837,13 @@ class Encoder extends Decoder { position--; } target[position++] = 251, targetView.setFloat64(position, value), position += 8; - } else if ("object" === type) if (value) { + } else value >= -24 ? target[position++] = 31 - value : value >= -256 ? (target[position++] = 56, + target[position++] = ~value) : value >= -65536 ? (target[position++] = 57, targetView.setUint16(position, ~value), + position += 2) : (target[position++] = 58, targetView.setUint32(position, ~value), + position += 4); else value < 24 ? target[position++] = value : value < 256 ? (target[position++] = 24, + target[position++] = value) : value < 65536 ? (target[position++] = 25, target[position++] = value >> 8, + target[position++] = 255 & value) : (target[position++] = 26, targetView.setUint32(position, value), + position += 4); else if ("object" === type) if (value) { if (referenceMap) { let referee = referenceMap.get(value); if (referee) { @@ -862,10 +887,14 @@ class Encoder extends Decoder { for (let entry of value) encode(entry); return void (target[position++] = 255); } - if (value[Symbol.asyncIterator] || constructor === BlobConstructor) { + if (value[Symbol.asyncIterator] || isBlob(value)) { let error = new Error("Iterable/blob should be serialized as iterator"); throw error.iteratorNotHandled = !0, error; } + if (this.useToJSON && value.toJSON) { + const json = value.toJSON(); + if (json !== value) return encode(json); + } writeObject(value, !value.hasOwnProperty); } } else target[position++] = 246; else if ("boolean" === type) target[position++] = value ? 245 : 244; else if ("bigint" === type) { @@ -884,7 +913,7 @@ class Encoder extends Decoder { if (length < 24 ? target[position++] = 160 | length : length < 256 ? (target[position++] = 184, target[position++] = length) : length < 65536 ? (target[position++] = 185, target[position++] = length >> 8, target[position++] = 255 & length) : (target[position++] = 186, targetView.setUint32(position, length), - position += 4), encoder.keyMap) for (let i = 0; i < length; i++) encode(encodeKey(keys[i])), + position += 4), encoder.keyMap) for (let i = 0; i < length; i++) encode(encoder.encodeKey(keys[i])), encode(vals[i]); else for (let i = 0; i < length; i++) encode(keys[i]), encode(vals[i]); } : (object, safePrototype) => { target[position++] = 185; @@ -952,7 +981,8 @@ class Encoder extends Decoder { useRecords || encode(key), value && "object" == typeof value ? iterateProperties[key] ? yield* encodeObjectAsIterable(value, iterateProperties[key]) : yield* tryEncode(value, iterateProperties, key) : encode(value); } } else if (constructor === Array) { - writeArrayHeader(object.length); + let length = object.length; + writeArrayHeader(length); for (let i = 0; i < length; i++) { let value = object[i]; value && ("object" == typeof value || position - start > chunkThreshold) ? iterateProperties.element ? yield* encodeObjectAsIterable(value, iterateProperties.element) : yield* tryEncode(value, iterateProperties, "element") : encode(value); @@ -961,7 +991,7 @@ class Encoder extends Decoder { target[position++] = 159; for (let value of object) value && ("object" == typeof value || position - start > chunkThreshold) ? iterateProperties.element ? yield* encodeObjectAsIterable(value, iterateProperties.element) : yield* tryEncode(value, iterateProperties, "element") : encode(value); target[position++] = 255; - } else constructor === BlobConstructor ? (writeEntityLength(object.size, 64), yield target.subarray(start, position), + } else isBlob(object) ? (writeEntityLength(object.size, 64), yield target.subarray(start, position), yield object, restartEncoding()) : object[Symbol.asyncIterator] ? (target[position++] = 159, yield target.subarray(start, position), yield object, restartEncoding(), target[position++] = 255) : encode(object); finalIterable && position > start ? yield target.subarray(start, position) : position - start > chunkThreshold && (yield target.subarray(start, position), @@ -987,7 +1017,7 @@ class Encoder extends Decoder { async function* encodeObjectAsAsyncIterable(value, iterateProperties) { for (let encodedValue of encodeObjectAsIterable(value, iterateProperties, !0)) { let constructor = encodedValue.constructor; - if (constructor === ByteArray || constructor === Uint8Array) yield encodedValue; else if (constructor === BlobConstructor) { + if (constructor === ByteArray || constructor === Uint8Array) yield encodedValue; else if (isBlob(encodedValue)) { let next, reader = encodedValue.stream().getReader(); for (;!(next = await reader.read()).done; ) yield next.value; } else if (encodedValue[Symbol.asyncIterator]) for await (let asyncValue of encodedValue) restartEncoding(), @@ -1038,6 +1068,14 @@ function writeArrayHeader(length) { position += 4); } +const BlobConstructor = "undefined" == typeof Blob ? function() {} : Blob; + +function isBlob(object) { + if (object instanceof BlobConstructor) return !0; + let tag = object[Symbol.toStringTag]; + return "Blob" === tag || "File" === tag; +} + function findRepetitiveStrings(value, packedValues) { switch (typeof value) { case "string": @@ -1075,7 +1113,7 @@ function typedArrayEncoder(tag, size) { tag, encode: function(typedArray, encode) { let length = typedArray.byteLength, offset = typedArray.byteOffset || 0, buffer = typedArray.buffer || typedArray; - encode(hasNodeBuffer ? Buffer.from(buffer, offset, length) : new Uint8Array(buffer, offset, length)); + encode(hasNodeBuffer ? Buffer$1.from(buffer, offset, length) : new Uint8Array(buffer, offset, length)); } }; } diff --git a/delivery/common/cwt/examples/typescript/src/cwt/cwt.d.ts b/delivery/common/cwt/examples/typescript/src/cwt/cwt.d.ts index dbadee73..80e29344 100644 --- a/delivery/common/cwt/examples/typescript/src/cwt/cwt.d.ts +++ b/delivery/common/cwt/examples/typescript/src/cwt/cwt.d.ts @@ -1,4 +1,4 @@ -/** @preserve @version 1.1.0 */ +/** @preserve @version 1.2.0 */ /** * Claim validation depends on integer label in the CWT claims set. @@ -7,7 +7,8 @@ */ declare type CWTOptions = { /** - * If CwtTag is prepended to the CWT token, we will verify the same. Default is set to false + * If CwtTag is prepended to the CWT token, we will verify the same. Default is set to false. + * During CWT generation, this flag can be used to add CWT tag for the token */ isCWTTagAdded?: boolean; /** @@ -16,6 +17,7 @@ declare type CWTOptions = { defaultCoseMsgType?: number; /** * If COSE CBOR is added to CWT token, Default is set to true. + * During CWT generation, this flag can be used to add COSE tag for the token */ isCoseCborTagAdded?: boolean; /** @@ -60,6 +62,34 @@ declare type CWTJSON = { }; payload: unknown; }; +/** + * Wrapper class to hold protected / unprotected header claims. + */ +declare type ContentHeader = { + p?: Map; + u?: Map; +}; +declare type Signer = { + key: CryptoKey; + p?: Map; + u?: Map; + externalAAD?: Uint8Array; +}; +declare type Verifier = { + key: CryptoKey; + externalAAD?: Uint8Array; + kid?: Uint8Array; +}; + +declare const HeaderLabels: { + [key: string]: number; +}; +declare const ClaimLabels: { + [key: string]: number; +}; +declare const AlgorithmLabels: { + [key: string]: number; +}; /** * CWTUtil contains helper APIs for converting base64/hex encoded string to binary and translating CWT payload/claims. @@ -73,21 +103,24 @@ declare class CWTUtil { */ static toHexString(byteArray: Uint8Array): string; /** - * Translates integer keys from the payload to the string keys from the labelsMap mapping. - * The value is passed to the traslator function before assigning to the keys. - * @param payload A JSON/Map object where keys are integer. @example { 1: 'john@issuer', 2: 'akamai@subject', 7: Uint8Array([72, 83, 50, 53, 54])} - * @param labelsMap A JSON object containing the mapping of integer keys used in CWT payload to string keys. @example { 1: 'iss', 2: 'sub', 3: 'aud', 4: 'exp', 5: 'nbf', 6: 'iat', 7: 'cti' } + * Translates keys of the map to the keys matched from the labelsMap. The value of the key from the paylaod is passed to the traslator function before assigning to the keys. + * @param payload A JSON/Map object where keys are integer or string. @example { 1: 'john@issuer', 2: 'akamai@subject', 7: Uint8Array([72, 83, 50, 53, 54])} + * @param labelsMap A JSON object containing the mapping of integer/string keys used in CWT payload to string/integer keys. @example { 1: 'iss', 2: 'sub', 3: 'aud', 4: 'exp', 5: 'nbf', 6: 'iat', 7: 'cti' } * @param tranlators A JSON object containing the mapping of field with the translator function. @example { cti: (input: Uint8Array) => TextDecoder().decode(input)} - * @returns Instance of JSON object where keys are string. @example { 'iss': 'john@issuer', 'sub': akamai@subject, 'cti': 'HS256' } + * @returns Instance of JSON object where keys are string or number. @example { 'iss': 'john@issuer', 'sub': akamai@subject, 'cti': 'HS256' } */ static claimsTranslate(payload: any, labelsMap: { - [key: number | string]: string; + [key: number | string]: string | number; }, translators?: { [key: string]: Function; - }): { - [key: string]: unknown; - [key: number]: unknown; - }; + }): Map; + /** + * Helper method to compare two Uint8Array + * @param arr1 Instance of Uint8Array + * @param arr2 Instance of Uint8Array + * @returns Boolean indicating result of comparsion + */ + static isUint8ArrayEqual(arr1: Uint8Array, arr2: Uint8Array): boolean; } /** @@ -111,20 +144,19 @@ declare class CWTValidator { * Decodes and performs signature validation one the CWT token. * It also validates the CWT payload claims and headers if validation is enabled via ${@link cwtOptions}. * @param tokenBuf CWT token in binary format (i.e Uint8Array) - * @param keys List of {@link CryptoKey } to be used for signature verification. Token is considered valid if signature is verifiable by any one key. - * @param externalAAD Externally supplied data in binary (i.e Uint8Array) that needs to be authenticated which is not carried as part of the COSE message. + * @param verifiers List of {@link Verifier } to be used for signature verification. Token is considered valid if signature is verifiable by any one key. Incase of multi signature cose structure, key matching the kid will be used for verification. * @returns Instance of {@link CWTJSON } containing header and payload * @throws {[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)} with appropriate message if type checks fails for arguments. * @throws {[DOMException](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) | [TypeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError)} when trying to use an invalid key data or when the key is not a key for the algorithm or when trying to use an algorithm that is either unknown or isn't suitable for a verify operation. * @example Error('Invalid arguments!') - If arguments types are invalid. * @example Error('CWT malformed: expected CWT CBOR tag for token!') - If cwtOptions.isCWTTagAdded is enabled but CWT token generated does not have CWTTag(61) added. */ - validate(tokenBuf: Uint8Array, keys: CryptoKey[], externalAAD?: Uint8Array): Promise; + validate(tokenBuf: Uint8Array, verifiers: Verifier[]): Promise; /** * Process COSE message structure and performs header validation if enabled. see {@link https://datatracker.ietf.org/doc/rfc8152/ } for COSE message spec used to generate CWT tokens. * @param coseMessage Array of cbor encode messages. * @param cwtType COSE message tag. - * @param keys List of keys in binary (i.e Uint8Array) used for verifying CWT token. + * @param verifiers List of {@link Verifier } to be used for signature verification. Token is considered valid if signature is verifiable by any one key. Incase of multi signature cose structure, key matching the kid will be used for verification. * @param headerValidation Boolean to enable/disable validation on header fields. * @param externalAAD Externally supplied data in binary (i.e Uint8Array) that needs to be authenticated which is not carried as part of the COSE message. * @returns Promise of {@link CWTJSON}. @@ -150,6 +182,40 @@ declare class CWTValidator { * @throws {[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)} if type check validation fails on {@link cwtOptions} fields. */ private validateOptionTypes; + /** + * Returns verifier whose key will be used to perform signature verification + * @param signU Unprotected header from Cose_Signature message structure + * @param verifiers List of verifiers with verifying key and kid. + * @returns Verifier whose kid matches the kid from signU (i.e unprotected header from Cose_Signature) + */ + private getVerifier; +} +/** + * CWTGenerator modules provides APIs to create CWT token. + * The module follows CWT tokens generated using spec defined here {@link https://www.rfc-editor.org/rfc/rfc8392.html}. + * As of now the module only supports COSE_Sign,COSE_Sign1, COSE_MAC0 and COSE_MAC structure. Also, Only ES256 and HS256 algorithms are supported at the moment. + * + */ +declare class CWTGenerator { + /** + * + * @param claims JSON object containing CWT claims set + * @param signer Instance of {@link Signer} which will used to sign the CWT token. One can pass external data to be included during signature computation. + * @param contentHeader JSON object containing protected and unprotected header fields. see {@link ContentHeader} for more details. + * @param recipients Recipients to which the signingKey needs to be shared with. (Note: currently not supported) + * @param options CWT options. see {@link CWTOptions} for more details. Only {@link CWTOptions.isCWTTagAdded} and {@link CWTOptions.isCoseCborTagAdded} are relevant during CWT token creation. + * @returns CWT token in {@link Uint8Array} + */ + static mac(claims: Map, signer: Signer, contentHeader: ContentHeader, recipients?: Signer[], options?: CWTOptions): Promise; + /** + * Generates CWT token based on assymetric cryptography scheme. + * @param claims JSON object containing CWT claims set + * @param signers Single or multiple signer(s) that will be used to sign the CWT token. One can pass external data for each signger that needs to be included during signature computation. If passed as array, COSE_Sign message structure is used or else COSE_Sign1 + * @param contentHeader JSON object containing protected and unprotected header fields. see {@link ContentHeader} for more details. + * @param options CWT options. see {@link CWTOptions} for more details. Only {@link CWTOptions.isCWTTagAdded} and {@link CWTOptions.isCoseCborTagAdded} are relevant during CWT token creation. + * @returns CWT token in {@link Uint8Array} + */ + static sign(claims: any, signers: Signer | Array, contentHeader?: ContentHeader, options?: CWTOptions): Promise; } -export { CWTJSON, CWTUtil, CWTValidator }; +export { AlgorithmLabels, CWTGenerator, CWTJSON, CWTUtil, CWTValidator, ClaimLabels, HeaderLabels, Signer, Verifier }; diff --git a/delivery/common/cwt/examples/typescript/src/cwt/cwt.js b/delivery/common/cwt/examples/typescript/src/cwt/cwt.js index caafead0..a207206b 100644 --- a/delivery/common/cwt/examples/typescript/src/cwt/cwt.js +++ b/delivery/common/cwt/examples/typescript/src/cwt/cwt.js @@ -1,21 +1,47 @@ -/** @preserve @version 1.1.1 */ +/** @preserve @version 1.2.0 */ import { crypto } from "crypto"; -import { Encoder, Decoder } from "./cbor-x.js"; +import { Encoder, Decoder, Tag } from "./cbor-x.js"; + +import { logger } from "log"; const COSE_Mac0 = 17, COSE_Mac = 97, COSE_Sign = 98, COSE_Sign1 = 18, COSE_Encrypt0 = 16, COSE_Encrypt = 96, coseAlgTags = { - 5: "HMAC 256/256", - "-7": "ES256" -}, HeaderLabelToKey_alg = 1, HeaderLabelToKey_crit = 2, claimsLabelToKey_iss = 1, claimsLabelToKey_sub = 2, claimsLabelToKey_aud = 3, claimsLabelToKey_exp = 4, claimsLabelToKey_nbf = 5; + "-7": "ES256", + 5: "HS256", + "-37": "PS256" +}, HeaderLabels = { + alg: 1, + crit: 2, + kid: 4 +}, ClaimLabels = { + iss: 1, + sub: 2, + aud: 3, + exp: 4, + nbf: 5, + iat: 6, + cti: 7 +}, AlgorithmLabels = { + ES256: -7, + HS256: 5, + PS256: -37 +}; class Mac { - static async verifyHMAC(alg, message, signature, keys) { - if ("HMAC 256/256" === alg) { - let isSignVerified = !1; - for (const key of keys) if (isSignVerified = await crypto.subtle.verify({ - name: "HMAC" - }, key, signature, message), isSignVerified) return Promise.resolve(isSignVerified); - return Promise.resolve(isSignVerified); + static async verifyHMAC(alg, message, signature, key) { + if ("HS256" === alg) return await crypto.subtle.verify({ + name: "HMAC" + }, key, signature, message); + throw new Error(`Unsupported Algorithm, ${alg}`); + } + static async doSign(signaturePayload, key, alg) { + if ("HS256" === alg) { + return await crypto.subtle.sign({ + name: "HMAC", + hash: { + name: "SHA-256" + } + }, key, signaturePayload); } throw new Error(`Unsupported Algorithm, ${alg}`); } @@ -28,28 +54,68 @@ class CWTUtil { })).join(""); } static claimsTranslate(payload, labelsMap, translators) { - const result = {}; - for (const param in payload) { - const key = labelsMap[param] ? labelsMap[param] : param, theValue = translators && translators[key] ? translators[key](payload[param]) : payload[param]; - result[key] = theValue; + if (payload instanceof Map) { + const result = new Map; + for (const [k, v] of payload) { + const tK = labelsMap[k] ? labelsMap[k] : k, tV = translators && translators[tK] ? translators[tK](v) : v; + result.set(tK, tV); + } + return result; } - return result; + { + const result = new Map; + for (const param in payload) { + const key = labelsMap[param] ? labelsMap[param] : param, theValue = translators && translators[key] ? translators[key](payload[param]) : payload[param]; + result.set(key, theValue); + } + return result; + } + } + static isUint8ArrayEqual(arr1, arr2) { + return arr1 instanceof Uint8Array && (arr2 instanceof Uint8Array && (arr1.length === arr2.length && arr1.every(((value, index) => value === arr2[index])))); } } CWTUtil.EMPTY_BUFFER = new Uint8Array(0); class Sign { - static async verifySignature(alg, message, signature, keys) { - if ("ES256" === alg) { - let isSignVerified = !1; - for (const key of keys) if (isSignVerified = await crypto.subtle.verify({ + static async verifySignature(alg, message, signature, key) { + switch (alg) { + case "ES256": + return await crypto.subtle.verify({ name: "ECDSA", hash: "SHA-256" - }, key, signature, new Uint8Array(message)), isSignVerified) return Promise.resolve(isSignVerified); - return Promise.resolve(isSignVerified); + }, key, signature, new Uint8Array(message)); + + case "PS256": + return await crypto.subtle.verify({ + name: "RSA-PSS", + saltLength: 32 + }, key, signature, new Uint8Array(message)); + + default: + throw new Error(`Unsupported Algorithm, ${alg}`); + } + } + static async doSign(signaturePayload, key, alg) { + switch (alg) { + case "ES256": + return await crypto.subtle.sign({ + name: "ECDSA", + hash: { + name: "SHA-256" + } + }, key, signaturePayload); + + case "PS256": + return await crypto.subtle.sign({ + name: "RSA-PSS", + saltLength: 32 + }, key, signaturePayload); + + default: + throw new Error(`Unsupported Algorithm, ${alg}`); } - throw new Error(`Unsupported Algorithm, ${alg}`); } } @@ -61,10 +127,9 @@ class CWTValidator { constructor(cwtOptions) { this.cwtOptions = cwtOptions || {}, this.validateOptionTypes(); } - async validate(tokenBuf, keys, externalAAD) { + async validate(tokenBuf, verifiers) { if (!(tokenBuf instanceof Uint8Array)) throw new Error("Invalid token type, expected Uint8Array!"); - if (externalAAD && !(externalAAD instanceof Uint8Array)) throw new Error("Invalid externalAAD type, expected Uint8Array!"); - if (!Array.isArray(keys) || !keys.every((elem => void 0 !== elem.type || void 0 !== elem.extractable || null != elem.algorithm || null != elem.usages))) throw new Error("Invalid keys type, expected list of CryptoKey!"); + if (verifiers.find((elem => void 0 === elem.key || void 0 === elem.key.type || null == elem.key.algorithm || null == elem.key.usages || elem.externalAAD && !(elem.externalAAD instanceof Uint8Array) || elem.kid && !(elem.kid instanceof Uint8Array)))) throw new Error("Invalid verifiers, expected list of verifier with valid crypto key!"); let coseMessage = globalThis.cbordec.decode(tokenBuf), cwtType = this.cwtOptions.defaultCoseMsgType; if (this.cwtOptions.isCWTTagAdded) { if (61 !== coseMessage.tag) throw new Error("CWT malformed: expected CWT CBOR tag for the token!"); @@ -74,27 +139,31 @@ class CWTValidator { if (cwtType = coseMessage.tag, ![ COSE_Mac0, COSE_Mac, COSE_Sign, COSE_Sign1, COSE_Encrypt, COSE_Encrypt0 ].includes(cwtType)) throw new Error("CWT malformed: invalid COSE CBOR tag!"); coseMessage = coseMessage.value; } - externalAAD || (externalAAD = CWTUtil.EMPTY_BUFFER); - const cwtJSON = await this.verifyCoseMessage(coseMessage, cwtType, keys, this.cwtOptions.headerValidation, externalAAD); + const cwtJSON = await this.verifyCoseMessage(coseMessage, cwtType, verifiers, this.cwtOptions.headerValidation); return this.validateClaims(cwtJSON.payload), cwtJSON; } - async verifyCoseMessage(coseMessage, cwtType, keys, headerValidation, externalAAD) { + async verifyCoseMessage(coseMessage, cwtType, verifiers, headerValidation) { switch (cwtType) { case COSE_Mac0: { if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR MAC0Tag, expected array of length 4!"); const [p, u, payload, tag] = coseMessage; let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; - pH = pH.size ? pH : CWTUtil.EMPTY_BUFFER; const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; let alg; - if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabelToKey_alg); else { + if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabels.alg); else { if (uH === CWTUtil.EMPTY_BUFFER) throw new Error("CWT malformed: unable to find algo field from CWT token."); - alg = u.get(HeaderLabelToKey_alg); + alg = uH.get(HeaderLabels.alg); } - alg = coseAlgTags[alg.toString()]; - const MACstructure = [ "MAC0", p, externalAAD, payload ], toBeMACed = globalThis.cborenc.encode(MACstructure); - if (!await Mac.verifyHMAC(alg, toBeMACed, tag, keys)) throw new Error("CWT token signature verification failed!"); + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER; + alg = coseAlgTags[alg]; + let isSignVerified = !1; + for (const verifier of verifiers) { + const MACstructure = [ "MAC0", pP, verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, payload ], toBeVerified = globalThis.cborenc.encode(MACstructure); + if (isSignVerified = await Mac.verifyHMAC(alg, toBeVerified, tag, verifier.key), + isSignVerified) break; + } + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); const decodedPayload = globalThis.cbordec.decode(payload); return Promise.resolve({ header: { @@ -108,18 +177,57 @@ class CWTValidator { case COSE_Sign1: { if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR COSE_Sign1, expected array of length 4!"); - const [p, u, payload, signer] = coseMessage; + const [p, u, payload, signature] = coseMessage; let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; pH = pH.size ? pH : CWTUtil.EMPTY_BUFFER; const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; let alg; - if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabelToKey_alg); else { + if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabels.alg); else { if (uH === CWTUtil.EMPTY_BUFFER) throw new Error("CWT malformed: unable to find algo field from CWT token."); - alg = u.get(HeaderLabelToKey_alg); + alg = u.get(HeaderLabels.alg); + } + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER; + alg = coseAlgTags[alg]; + let isSignVerified = !1; + for (const verifier of verifiers) { + const SigStructure = [ "Signature1", pP, verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, payload ], toBeVerified = globalThis.cborenc.encode(SigStructure); + if (isSignVerified = await Sign.verifySignature(alg, toBeVerified, signature, verifier.key), + isSignVerified) break; } - alg = coseAlgTags[alg.toString()]; - const SigStructure = [ "Signature1", p, externalAAD, payload ], toBeVeried = globalThis.cborenc.encode(SigStructure); - if (!await Sign.verifySignature(alg, toBeVeried, signer, keys)) throw new Error("CWT token signature verification failed!"); + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); + const decodedPayload = globalThis.cbordec.decode(payload); + return Promise.resolve({ + header: { + p: pH, + u: uH + }, + payload: decodedPayload + }); + } + + case COSE_Sign: + { + if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR COSE_Sign, expected array of length 4!"); + const [p, u, payload, signatures] = coseMessage; + let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; + const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; + headerValidation && this.validateHeader(pH, !0); + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER, isSignVerified = !1; + for (const signature of signatures) { + let [signP, signU, sign] = signature; + const verifier = this.getVerifier(signU, verifiers); + if (verifier) { + const externalAAD = verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, signerPMap = signP.length ? globalThis.cborenc.decode(signP) : CWTUtil.EMPTY_BUFFER; + signP = signerPMap.size ? signP : CWTUtil.EMPTY_BUFFER; + const alg = signerPMap.get ? signerPMap.get(HeaderLabels.alg) : pH.get ? pH.get(HeaderLabels.alg) : void 0; + if (alg) { + const SigStructure = [ "Signature", pP, signP, externalAAD, payload ], toBeVerified = globalThis.cborenc.encode(SigStructure); + if (isSignVerified = await Sign.verifySignature(coseAlgTags[alg], toBeVerified, sign, verifier.key), + isSignVerified) break; + } else logger.error(`Unable to find alg in CWT token for kid = ${verifier.kid}, hence skipping the verifier!`); + } + } + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); const decodedPayload = globalThis.cbordec.decode(payload); return Promise.resolve({ header: { @@ -136,7 +244,7 @@ class CWTValidator { } validateHeader(headers, pheader) { if (headers.size && pheader) { - const h = headers, crit = h.get(HeaderLabelToKey_crit); + const h = headers, crit = h.get(HeaderLabels.crit); if (Array.isArray(crit)) { if (0 === crit.length) throw new Error("CWT Malformed: malformed protected header, crit array cannot be empty!"); for (const e of crit) if (void 0 === h.get(e)) throw new Error("CWT Malformed: malformed protected header, crit labels are not part of protected header"); @@ -145,29 +253,29 @@ class CWTValidator { } validateClaims(decodedPayload) { if (this.cwtOptions.issuer) { - const iss = decodedPayload.get(claimsLabelToKey_iss); + const iss = decodedPayload.get(ClaimLabels.iss); if (iss && this.cwtOptions.issuer !== iss) throw new Error(`CWT malformed: invalid iss, expected ${this.cwtOptions.issuer}`); } if (this.cwtOptions.subject) { - const sub = decodedPayload.get(claimsLabelToKey_sub); + const sub = decodedPayload.get(ClaimLabels.sub); if (sub && this.cwtOptions.subject !== sub) throw new Error(`CWT malformed: invalid sub, expected ${this.cwtOptions.subject}`); } if (this.cwtOptions.audience) { - const aud = decodedPayload.get(claimsLabelToKey_aud); + const aud = decodedPayload.get(ClaimLabels.aud); if (aud && (Array.isArray(aud) || "string" == typeof aud)) { if (!(Array.isArray(aud) ? aud : [ aud ]).includes(this.cwtOptions.audience)) throw new Error(`CWT malformed: invalid aud, expected ${this.cwtOptions.audience}`); } } const clockTimestamp = Math.floor(Date.now() / 1e3); if (!1 === this.cwtOptions.ignoreExpiration) { - const exp = decodedPayload.get(claimsLabelToKey_exp); + const exp = decodedPayload.get(ClaimLabels.exp); if (exp) { if ("number" != typeof exp) throw new Error("CWT malformed: exp must be number"); if (clockTimestamp > exp + (this.cwtOptions.clockTolerance || 0)) throw new Error("CWT token has been expired"); } } if (!1 === this.cwtOptions.ignoreNotBefore) { - const nbf = decodedPayload.get(claimsLabelToKey_nbf); + const nbf = decodedPayload.get(ClaimLabels.nbf); if (nbf) { if ("number" != typeof nbf) throw new Error("CWT malformed: nbf must be number"); if (nbf > clockTimestamp + (this.cwtOptions.clockTolerance || 0)) throw new Error("CWT is not active"); @@ -187,6 +295,66 @@ class CWTValidator { if (void 0 !== this.cwtOptions.clockTolerance && "number" != typeof this.cwtOptions.clockTolerance) throw new Error("Invalid cwtOptions: clockTolerance must be number"); this.cwtOptions.clockTolerance = 60; } + getVerifier(signU, verifiers) { + const kid = signU.get(HeaderLabels.kid); + for (const verifier of verifiers) if (CWTUtil.isUint8ArrayEqual(kid, verifier.kid)) return verifier; + return null; + } +} + +class CWTGenerator { + static async mac(claims, signer, contentHeader, recipients, options) { + if (!claims) throw new Error("Invalid claims type, cannot be null or undefined!"); + if (void 0 === signer.key || void 0 === signer.key.type || null == signer.key.algorithm || null == signer.key.usages || signer.externalAAD && !(signer.externalAAD instanceof Uint8Array)) throw new Error("Invalid signer, expected signer with valid crypto key!"); + if (!contentHeader) throw new Error("Invalid contentHeader type, cannot be null or undefined!"); + options = options || {}; + let pH = contentHeader && contentHeader.p ? contentHeader.p : new Map, uH = contentHeader && contentHeader.u ? contentHeader.u : new Map, protectedHeader = 0 === pH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(pH), alg = pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader!"); + let result, payload = globalThis.cborenc.encode(claims); + if (Array.isArray(recipients)) { + if (0 === recipients.length) throw new Error("No recipients found, there has to be atleast one recipients!"); + if (recipients.length > 0) throw new Error("Mac with recipients is not currently supported!"); + } else { + const MACstructure = [ "MAC0", protectedHeader, signer.externalAAD || CWTUtil.EMPTY_BUFFER, payload ]; + let signed = globalThis.cborenc.encode(MACstructure); + result = [ protectedHeader, uH, payload, await Mac.doSign(signed, signer.key, coseAlgTags[alg]) ], + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(result, COSE_Mac0) : result; + } + return result = options.isCWTTagAdded ? new Tag(result, 61) : result, globalThis.cborenc.encode(result); + } + static async sign(claims, signers, contentHeader, options) { + if (!claims) throw new Error("Invalid claims type, cannot be null or undefined!"); + if (!signers || Array.isArray(signers) && 0 == signers.length) throw new Error("Invalid signers, cannot be null or undefined, requies at atleast one signer!"); + if (Array.isArray(signers)) { + if (signers.find((elem => void 0 === elem.key || void 0 === elem.key.type || null == elem.key.algorithm || null == elem.key.usages || elem.externalAAD && !(elem.externalAAD instanceof Uint8Array)))) throw new Error("Invalid signers, expected list of signers with valid crypto key!"); + } else if (void 0 === signers.key || void 0 === signers.key.type || null == signers.key.algorithm || null == signers.key.usages || signers.externalAAD && !(signers.externalAAD instanceof Uint8Array)) throw new Error("Invalid signer, expected signer with valid crypto key!"); + options = options || {}; + let result, pH = contentHeader && contentHeader.p ? contentHeader.p : new Map, uH = contentHeader && contentHeader.u ? contentHeader.u : new Map, protectedHeader = 0 === pH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(pH), payload = globalThis.cborenc.encode(claims); + if (Array.isArray(signers)) { + let signatures = Array(); + for (const signer of signers) { + const externalAAD = signer.externalAAD || CWTUtil.EMPTY_BUFFER; + let signPH = signer.p ? signer.p : new Map, signUH = signer.u ? signer.u : new Map, alg = signPH.get(HeaderLabels.alg) || pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader or Signer object!"); + let signprotectedHeader = 0 === signPH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(signPH); + const SigStructure = [ "Signature", protectedHeader, signprotectedHeader, externalAAD, payload ]; + let signaturePayload = globalThis.cborenc.encode(SigStructure); + const sig = await Sign.doSign(signaturePayload, signer.key, coseAlgTags[alg]); + signatures.push([ signprotectedHeader, signUH, sig ]); + } + let signed = [ protectedHeader, uH, payload, signatures ]; + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(signed, COSE_Sign) : signed; + } else { + const externalAAD = signers.externalAAD || CWTUtil.EMPTY_BUFFER; + let alg = pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader!"); + const SigStructure = [ "Signature1", protectedHeader, externalAAD, payload ]; + let signaturePayload = globalThis.cborenc.encode(SigStructure); + let signed = [ protectedHeader, uH, payload, await Sign.doSign(signaturePayload, signers.key, coseAlgTags[alg]) ]; + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(signed, COSE_Sign1) : signed; + } + return result = options.isCWTTagAdded ? new Tag(result, 61) : result, globalThis.cborenc.encode(result); + } } -export { CWTUtil, CWTValidator }; +export { AlgorithmLabels, CWTGenerator, CWTUtil, CWTValidator, ClaimLabels, HeaderLabels }; diff --git a/delivery/common/cwt/examples/typescript/src/main.ts b/delivery/common/cwt/examples/typescript/src/main.ts index ab1cbf07..01baf453 100644 --- a/delivery/common/cwt/examples/typescript/src/main.ts +++ b/delivery/common/cwt/examples/typescript/src/main.ts @@ -1,73 +1,93 @@ import { logger } from 'log'; -import { crypto } from "crypto"; -import { CWTJSON, CWTUtil, CWTValidator} from './cwt/cwt'; +import { CWTUtil, CWTValidator, CWTGenerator, AlgorithmLabels, ClaimLabels, HeaderLabels, Signer, Verifier} from './cwt/cwt'; +import { crypto, pem2ab } from 'crypto'; import { base16 } from 'encoding'; - +//Integer keys mapping for CWT payload. This mapping is application specific, However keys from 1-7 are reserved const claimsLabelMap = { - 1: 'iss', 2: 'sub', 3: 'aud', 4: 'exp', 5: 'nbf', - 6: 'iat', - 7: 'cti', - 200: 'wmver', - 201: 'wmvnd', - 202: 'wmidtyp', - 203: 'wmidfmt', - 204: 'wmpatlen', - 205: 'wmid', - 206: 'wmsegduration', - 207: 'wmidalg', - 208: 'wmidivlen', - 209: 'wmidivhex', - 210: 'wmidpid', - 211: 'wmidpalg', - 212: 'wmidkeyver', - 213: 'wmopid' + 6: 'iat' }; - + +//advanced options for cwt validator const cwtOptions = { - headerValidation: true + //perform header validation + headerValidation: false, + //check token expiry + ignoreExpiration: true, + //check token nbf + ignoreNotBefore: true }; +export const es256PrivKey1 = { + key_ops: ['sign'], + ext: false, + kty: 'EC', + x: 'D5fNFnQYFBOjWa1ndpQK3ZrzXuHD77oGDgPaMNbtZ7s', + y: 'Y4iS6G8atqp3x85xJOfCY997AVWHPy-dEgLk6CaNZ7w', + crv: 'P-256', + d: 'CyJoz5l2IG9cPEXvPATnU3BHrNS1Qx5-dZ4e_Z0H_3M' +}; + +const es256PubKey1 = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAED5fNFnQYFBOjWa1ndpQK3ZrzXuHD +77oGDgPaMNbtZ7tjiJLobxq2qnfHznEk58Jj33sBVYc/L50SAuToJo1nvA== +-----END PUBLIC KEY-----`; + const cwtValidator = new CWTValidator(cwtOptions); export async function onClientRequest (request: EW.IngressClientRequest) { + try { - // Fetch hmac veification key from Propery Manager - const secretKey = request.getVariable('PMUSER_CWT_HMAC_KEY'); - const sKey = await crypto.subtle.importKey( - 'raw', - (base16.decode(secretKey as string, 'Uint8Array') as Uint8Array).buffer, + const signingKey = await crypto.subtle.importKey( + 'jwk', + es256PrivKey1, { - name: 'HMAC', - hash: 'SHA-256' + name: 'ECDSA', + namedCurve: 'P-256' }, - false, - ['verify'] + !1, + ['sign'] ); - //Fetch the Authorization header from request - const cwts = request.getHeader('Authorization'); - if (cwts){ - let cwt = cwts[0]; - //replace auth scheme before validating - cwt = cwt.replace('Bearer ',''); - //Assumption: CWT token as passed as hex encoded in authorization header. We decode the hex to get the binary - const tokenBuf = base16.decode(cwt,'Uint8Array') as Uint8Array; - const cwtJSON = await cwtValidator.validate(tokenBuf,[sKey as CryptoKey]) as CWTJSON; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const claims = CWTUtil.claimsTranslate(Object.fromEntries(new Map(cwtJSON.payload as any)),claimsLabelMap); - logger.log('cwtJSON %s: ',JSON.stringify(claims)); - request.respondWith(200, {}, JSON.stringify(claims)); + if (request.path == '/token' && request.method == 'POST') { + const claims = { iss: 'mde_dev@akamai.com', iat: Date.now(), sub: "subject@akamai.com", aud: ["cdn@akamai.com"], exp: Date.now() + 86400, nbf: Date.now() - 86400}; + const claimsSet = CWTUtil.claimsTranslate(claims, ClaimLabels); + const signer: Signer = { + key: signingKey as CryptoKey + }; + const cwtToken = await CWTGenerator.sign(claimsSet, signer, { p: CWTUtil.claimsTranslate({ alg: AlgorithmLabels.ES256 }, HeaderLabels)}); + const cwtTokenHex = base16.encode(new Uint8Array(cwtToken)); + request.respondWith(200, {}, cwtTokenHex); } else { - //Return bad request of authorization header is not found - request.respondWith(400, {}, 'Authorization header is missing!'); + const verifyKey = await crypto.subtle.importKey( + 'spki', + pem2ab(es256PubKey1), + { name: "ECDSA", namedCurve: "P-256" }, + false, + ['verify'] + ); + //Fetch the Authorization header from request + let cwtToken = request.getHeader('Authorization'); + if (cwtToken){ + let cwtT = cwtToken[0]; + //replace auth scheme before validating + cwtT = cwtT.replace('Bearer ',''); + //Assumption: CWT token as passed as hex encoded in authorization header. We decode the hex to get the binary + const tokenBuf = base16.decode(cwtT,'Uint8Array') as Uint8Array; + const cwtJSON = await cwtValidator.validate(tokenBuf,[{ key: verifyKey} as Verifier]); + const claims = CWTUtil.claimsTranslate(cwtJSON.payload,claimsLabelMap); + request.respondWith(200, {}, JSON.stringify(Object.fromEntries(claims))); + } else { + //Return bad request of authorization header is not found + request.respondWith(400, {}, 'Authorization header is missing!'); + } } - } catch (error: unknown) { - logger.log('Error: %s',(error as Error).message); - request.respondWith(400, {}, JSON.stringify({error: (error as Error).message})); + } catch (error) { + logger.log(error as any); + request.respondWith(400, {}, (error as any).message); } -} +} \ No newline at end of file diff --git a/delivery/common/cwt/lib/README.md b/delivery/common/cwt/lib/README.md index b7e945c4..96dab933 100644 --- a/delivery/common/cwt/lib/README.md +++ b/delivery/common/cwt/lib/README.md @@ -1,21 +1,21 @@ # CWT Module -The CWT module can be used to perform operations related to CWT tokens such as CWT token generation and validation. The module considers CWT tokens defined as per this [spec](https://www.rfc-editor.org/rfc/rfc8392.html). It exports implementations of CWTValidator class that contains API's to validate CWT tokens. +The CWT module can be used to perform operations related to CWT tokens such as CWT token generation and validation. The module considers CWT tokens defined as per this [spec](https://www.rfc-editor.org/rfc/rfc8392.html). It exports implementations of CWTValidator, CWTGenerator class that contains API's to validate/sign CWT tokens respectively. ## Limitations -- Currently the module only support API's for verification of CWT tokens that are generated using HS256, ES256 algorithm only. +- Currently the module only support API's for signature and verification of CWT tokens that are generated using HS256, ES256, PS256 algorithm only. - Currently the module only support MAC0 and Sign1 COSE message structure. Refer this [page](https://datatracker.ietf.org/doc/rfc8152/) for more details on CBOR Object Signing and Encryption (COSE). - As of now, EW do not support KMI to manage verification keys, hence these keys are fetched from property manager user defined variable which might not be a secure way. More details on user defined variables can be found [here](https://techdocs.akamai.com/property-mgr/docs/user-defined-vars) -## Files -* **cwt.js** is the main class you import in your main.js file. This file provides helper classes such as CWTValidator for validating CWT tokens. -* **cwt.d.ts** is the TypeScript definition file for CWT module. - -## Documentation -Please visit this [page](https://techdocs.akamai.com/edgeworkers/docs/cwt) for complete documentation and usage of CWT module. +## Subfolder organization +* **/apis**: Link to CWT API documentation. +* **/lib**: CWT module (js) and typescript definition. +* **/examples**: Usage examples of CWT module. ## Resources -Please see the examples [here](../examples/) for example usage of CWT module. +For more information on CWT Module, please refer to the following resources: +* [CWT API Documentation](https://techdocs.akamai.com/edgeworkers/docs/cwt) +* [Examples](./examples/) -### Todo -- [ ] Add documentation page link. +## Reporting Issues +If you experience any problems, please raise a Github issue or create a pull request with fixes, suggestions, or code contributions. diff --git a/delivery/common/cwt/lib/cbor-x.js b/delivery/common/cwt/lib/cbor-x.js index 6e40b011..05f1b05e 100644 --- a/delivery/common/cwt/lib/cbor-x.js +++ b/delivery/common/cwt/lib/cbor-x.js @@ -11,7 +11,13 @@ const RECORD_DEFINITIONS_ID = 57342, RECORD_INLINE_ID = 57343, BUNDLED_STRINGS_I let currentStructures, srcString, bundledStrings$1, referenceMap, packedValues, dataView, restoreMapsAsObject, currentDecoder = {}, srcStringStart = 0, srcStringEnd = 0, currentExtensions = [], currentExtensionRanges = [], defaultOptions = { useRecords: !1, mapsAsObjects: !0 -}, sequentialMode = !1; +}, sequentialMode = !1, inlineObjectReadThreshold = 2; + +try { + new Function(""); +} catch (error) { + inlineObjectReadThreshold = 1 / 0; +} class Decoder { constructor(options) { @@ -201,9 +207,7 @@ function read() { return ~token; case 2: - return function(length) { - return currentDecoder.copyBuffers ? Uint8Array.prototype.slice.call(src, position$1, position$1 += length) : src.subarray(position$1, position$1 += length); - }(token); + return length = token, currentDecoder.copyBuffers ? Uint8Array.prototype.slice.call(src, position$1, position$1 += length) : src.subarray(position$1, position$1 += length); case 3: if (srcStringEnd >= position$1) return srcString.slice(position$1 - srcStringStart, (position$1 += token) - srcStringStart); @@ -245,10 +249,20 @@ function read() { if (structure) return structure.read || (structure.read = createStructureReader(structure)), structure.read(); if (token < 65536) { - if (token == RECORD_INLINE_ID) return recordDefinition(read()); + if (token == RECORD_INLINE_ID) { + let length = readJustLength(), id = read(), structure = read(); + recordDefinition(id, structure); + let object = {}; + if (currentDecoder.keyMap) for (let i = 2; i < length; i++) { + object[safeKey(currentDecoder.decodeKey(structure[i - 2]))] = read(); + } else for (let i = 2; i < length; i++) { + object[safeKey(structure[i - 2])] = read(); + } + return object; + } if (token == RECORD_DEFINITIONS_ID) { let length = readJustLength(), id = read(); - for (let i = 2; i < length; i++) recordDefinition([ id++, read() ]); + for (let i = 2; i < length; i++) recordDefinition(id++, read()); return read(); } if (token == BUNDLED_STRINGS_ID) return function() { @@ -305,6 +319,7 @@ function read() { } throw new Error("Unknown CBOR token " + token); } + var length; } const validName = /^[a-zA-Z_$][a-zA-Z\d_$]*$/; @@ -333,7 +348,7 @@ function createStructureReader(structure) { if (compiledReader.propertyCount === length) return compiledReader(read); compiledReader = compiledReader.next; } - if (this.slowReads++ >= 3) { + if (this.slowReads++ >= inlineObjectReadThreshold) { let array = this.length == length ? this : this.slice(0, length); return compiledReader = currentDecoder.keyMap ? new Function("r", "return {" + array.map((k => currentDecoder.decodeKey(k))).map((k => validName.test(k) ? safeKey(k) + ":r()" : "[" + JSON.stringify(k) + "]:r()")).join(",") + "}") : new Function("r", "return {" + array.map((key => validName.test(key) ? safeKey(key) + ":r()" : "[" + JSON.stringify(key) + "]:r()")).join(",") + "}"), this.compiledReader && (compiledReader.next = this.compiledReader), compiledReader.propertyCount = length, @@ -346,7 +361,9 @@ function createStructureReader(structure) { } function safeKey(key) { - return "__proto__" === key ? "__proto_" : key; + if ("string" == typeof key) return "__proto__" === key ? "__proto_" : key; + if ("object" != typeof key) return key.toString(); + throw new Error("Invalid property name type " + typeof key); } let readFixedString = readStringJS, isNativeAccelerationEnabled = !1; @@ -467,20 +484,21 @@ currentExtensions[2] = buffer => { }, currentExtensions[3] = buffer => BigInt(-1) - currentExtensions[2](buffer), currentExtensions[4] = fraction => +(fraction[1] + "e" + fraction[0]), currentExtensions[5] = fraction => fraction[1] * Math.exp(fraction[0] * Math.log(2)); -const recordDefinition = definition => { - let id = definition[0] - 57344, structure = definition[1], existingStructure = currentStructures[id]; +const recordDefinition = (id, structure) => { + let existingStructure = currentStructures[id -= 57344]; existingStructure && existingStructure.isShared && ((currentStructures.restoreStructures || (currentStructures.restoreStructures = []))[id] = existingStructure), currentStructures[id] = structure, structure.read = createStructureReader(structure); +}; + +currentExtensions[105] = data => { + let length = data.length, structure = data[1]; + recordDefinition(data[0], structure); let object = {}; - if (currentDecoder.keyMap) for (let i = 2, l = definition.length; i < l; i++) { - object[safeKey(currentDecoder.decodeKey(structure[i - 2]))] = definition[i]; - } else for (let i = 2, l = definition.length; i < l; i++) { - object[safeKey(structure[i - 2])] = definition[i]; + for (let i = 2; i < length; i++) { + object[safeKey(structure[i - 2])] = data[i]; } return object; -}; - -currentExtensions[105] = recordDefinition, currentExtensions[14] = value => bundledStrings$1 ? bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 += value) : new Tag(value, 14), +}, currentExtensions[14] = value => bundledStrings$1 ? bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 += value) : new Tag(value, 14), currentExtensions[15] = value => bundledStrings$1 ? bundledStrings$1[1].slice(bundledStrings$1.position1, bundledStrings$1.position1 += value) : new Tag(value, 15); let glbl = { @@ -491,8 +509,15 @@ let glbl = { currentExtensions[27] = data => (glbl[data[0]] || Error)(data[1], data[2]); const packedTable = read => { - if (132 != src[position$1++]) throw new Error("Packed values structure must be followed by a 4 element array"); + if (132 != src[position$1++]) { + let error = new Error("Packed values structure must be followed by a 4 element array"); + throw src.length < position$1 && (error.incomplete = !0), error; + } let newPackedValues = read(); + if (!newPackedValues || !newPackedValues.length) { + let error = new Error("Packed values structure must be followed by a 4 element array"); + throw error.incomplete = !0, error; + } return packedValues = packedValues ? newPackedValues.concat(packedValues.slice(newPackedValues.length)) : newPackedValues, packedValues.prefixes = read(), packedValues.suffixes = read(), read(); }; @@ -515,7 +540,8 @@ packedTable.handlesRead = !0, currentExtensions[51] = packedTable, currentExtens loadShared(); } if ("number" == typeof data) return packedValues[16 + (data >= 0 ? 2 * data : -2 * data - 1)]; - throw new Error("No support for non-integer packed references yet"); + let error = new Error("No support for non-integer packed references yet"); + throw void 0 === data && (error.incomplete = !0), error; }, currentExtensions[28] = read => { referenceMap || (referenceMap = new Map, referenceMap.id = 0); let target, id = referenceMap.id++; @@ -548,15 +574,14 @@ const isLittleEndianMachine$1 = 1 == new Uint8Array(new Uint16Array([ 1 ]).buffe for (let i = 0; i < typedArrays.length; i++) registerTypedArray(typedArrays[i], typedArrayTags[i]); function registerTypedArray(TypedArray, tag) { - let dvMethod = "get" + TypedArray.name.slice(0, -5); - "function" != typeof TypedArray && (TypedArray = null); - let bytesPerElement = TypedArray.BYTES_PER_ELEMENT; + let bytesPerElement, dvMethod = "get" + TypedArray.name.slice(0, -5); + "function" == typeof TypedArray ? bytesPerElement = TypedArray.BYTES_PER_ELEMENT : TypedArray = null; for (let littleEndian = 0; littleEndian < 2; littleEndian++) { if (!littleEndian && 1 == bytesPerElement) continue; let sizeShift = 2 == bytesPerElement ? 1 : 4 == bytesPerElement ? 2 : 3; currentExtensions[littleEndian ? tag : tag - 4] = 1 == bytesPerElement || littleEndian == isLittleEndianMachine$1 ? buffer => { if (!TypedArray) throw new Error("Could not find typed array for code " + tag); - return new TypedArray(Uint8Array.prototype.slice.call(buffer, 0).buffer); + return currentDecoder.copyBuffers || 1 !== bytesPerElement && (2 !== bytesPerElement || 1 & buffer.byteOffset) && (4 !== bytesPerElement || 3 & buffer.byteOffset) && (8 !== bytesPerElement || 7 & buffer.byteOffset) ? new TypedArray(Uint8Array.prototype.slice.call(buffer, 0).buffer) : new TypedArray(buffer.buffer, buffer.byteOffset, buffer.byteLength); } : buffer => { if (!TypedArray) throw new Error("Could not find typed array for code " + tag); let dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength), elements = buffer.length >> sizeShift, ta = new TypedArray(elements), method = dv[dvMethod]; @@ -631,7 +656,7 @@ try { textEncoder = new TextEncoder; } catch (error) {} -const hasNodeBuffer = "undefined" != typeof Buffer, ByteArrayAllocate = hasNodeBuffer ? Buffer.allocUnsafeSlow : Uint8Array, ByteArray = hasNodeBuffer ? Buffer : Uint8Array, BlobConstructor = "undefined" == typeof Blob ? {} : Blob, MAX_BUFFER_SIZE = hasNodeBuffer ? 4294967296 : 2144337920; +const Buffer$1 = "object" == typeof globalThis && globalThis.Buffer, hasNodeBuffer = void 0 !== Buffer$1, ByteArrayAllocate = hasNodeBuffer ? Buffer$1.allocUnsafeSlow : Uint8Array, ByteArray = hasNodeBuffer ? Buffer$1 : Uint8Array, MAX_BUFFER_SIZE = hasNodeBuffer ? 4294967296 : 2144337920; let throwOnIterable, target, targetView, safeEnd, position = 0, bundledStrings = null; @@ -804,13 +829,7 @@ class Encoder extends Decoder { target[position++] = 121, target[position++] = length >> 8, target[position++] = 255 & length) : (headerSize < 5 && target.copyWithin(position + 5, position + 3, position + 3 + length), target[position++] = 122, targetView.setUint32(position, length), position += 4), position += length; - } else if ("number" === type) if (value >>> 0 === value) value < 24 ? target[position++] = value : value < 256 ? (target[position++] = 24, - target[position++] = value) : value < 65536 ? (target[position++] = 25, target[position++] = value >> 8, - target[position++] = 255 & value) : (target[position++] = 26, targetView.setUint32(position, value), - position += 4); else if (value >> 0 === value) value >= -24 ? target[position++] = 31 - value : value >= -256 ? (target[position++] = 56, - target[position++] = ~value) : value >= -65536 ? (target[position++] = 57, targetView.setUint16(position, ~value), - position += 2) : (target[position++] = 58, targetView.setUint32(position, ~value), - position += 4); else { + } else if ("number" === type) if (this.alwaysUseFloat || value >>> 0 !== value) if (this.alwaysUseFloat || value >> 0 !== value) { let useFloat32; if ((useFloat32 = this.useFloat32) > 0 && value < 4294967296 && value >= -2147483648) { let xShifted; @@ -818,7 +837,13 @@ class Encoder extends Decoder { position--; } target[position++] = 251, targetView.setFloat64(position, value), position += 8; - } else if ("object" === type) if (value) { + } else value >= -24 ? target[position++] = 31 - value : value >= -256 ? (target[position++] = 56, + target[position++] = ~value) : value >= -65536 ? (target[position++] = 57, targetView.setUint16(position, ~value), + position += 2) : (target[position++] = 58, targetView.setUint32(position, ~value), + position += 4); else value < 24 ? target[position++] = value : value < 256 ? (target[position++] = 24, + target[position++] = value) : value < 65536 ? (target[position++] = 25, target[position++] = value >> 8, + target[position++] = 255 & value) : (target[position++] = 26, targetView.setUint32(position, value), + position += 4); else if ("object" === type) if (value) { if (referenceMap) { let referee = referenceMap.get(value); if (referee) { @@ -862,10 +887,14 @@ class Encoder extends Decoder { for (let entry of value) encode(entry); return void (target[position++] = 255); } - if (value[Symbol.asyncIterator] || constructor === BlobConstructor) { + if (value[Symbol.asyncIterator] || isBlob(value)) { let error = new Error("Iterable/blob should be serialized as iterator"); throw error.iteratorNotHandled = !0, error; } + if (this.useToJSON && value.toJSON) { + const json = value.toJSON(); + if (json !== value) return encode(json); + } writeObject(value, !value.hasOwnProperty); } } else target[position++] = 246; else if ("boolean" === type) target[position++] = value ? 245 : 244; else if ("bigint" === type) { @@ -884,7 +913,7 @@ class Encoder extends Decoder { if (length < 24 ? target[position++] = 160 | length : length < 256 ? (target[position++] = 184, target[position++] = length) : length < 65536 ? (target[position++] = 185, target[position++] = length >> 8, target[position++] = 255 & length) : (target[position++] = 186, targetView.setUint32(position, length), - position += 4), encoder.keyMap) for (let i = 0; i < length; i++) encode(encodeKey(keys[i])), + position += 4), encoder.keyMap) for (let i = 0; i < length; i++) encode(encoder.encodeKey(keys[i])), encode(vals[i]); else for (let i = 0; i < length; i++) encode(keys[i]), encode(vals[i]); } : (object, safePrototype) => { target[position++] = 185; @@ -952,7 +981,8 @@ class Encoder extends Decoder { useRecords || encode(key), value && "object" == typeof value ? iterateProperties[key] ? yield* encodeObjectAsIterable(value, iterateProperties[key]) : yield* tryEncode(value, iterateProperties, key) : encode(value); } } else if (constructor === Array) { - writeArrayHeader(object.length); + let length = object.length; + writeArrayHeader(length); for (let i = 0; i < length; i++) { let value = object[i]; value && ("object" == typeof value || position - start > chunkThreshold) ? iterateProperties.element ? yield* encodeObjectAsIterable(value, iterateProperties.element) : yield* tryEncode(value, iterateProperties, "element") : encode(value); @@ -961,7 +991,7 @@ class Encoder extends Decoder { target[position++] = 159; for (let value of object) value && ("object" == typeof value || position - start > chunkThreshold) ? iterateProperties.element ? yield* encodeObjectAsIterable(value, iterateProperties.element) : yield* tryEncode(value, iterateProperties, "element") : encode(value); target[position++] = 255; - } else constructor === BlobConstructor ? (writeEntityLength(object.size, 64), yield target.subarray(start, position), + } else isBlob(object) ? (writeEntityLength(object.size, 64), yield target.subarray(start, position), yield object, restartEncoding()) : object[Symbol.asyncIterator] ? (target[position++] = 159, yield target.subarray(start, position), yield object, restartEncoding(), target[position++] = 255) : encode(object); finalIterable && position > start ? yield target.subarray(start, position) : position - start > chunkThreshold && (yield target.subarray(start, position), @@ -987,7 +1017,7 @@ class Encoder extends Decoder { async function* encodeObjectAsAsyncIterable(value, iterateProperties) { for (let encodedValue of encodeObjectAsIterable(value, iterateProperties, !0)) { let constructor = encodedValue.constructor; - if (constructor === ByteArray || constructor === Uint8Array) yield encodedValue; else if (constructor === BlobConstructor) { + if (constructor === ByteArray || constructor === Uint8Array) yield encodedValue; else if (isBlob(encodedValue)) { let next, reader = encodedValue.stream().getReader(); for (;!(next = await reader.read()).done; ) yield next.value; } else if (encodedValue[Symbol.asyncIterator]) for await (let asyncValue of encodedValue) restartEncoding(), @@ -1038,6 +1068,14 @@ function writeArrayHeader(length) { position += 4); } +const BlobConstructor = "undefined" == typeof Blob ? function() {} : Blob; + +function isBlob(object) { + if (object instanceof BlobConstructor) return !0; + let tag = object[Symbol.toStringTag]; + return "Blob" === tag || "File" === tag; +} + function findRepetitiveStrings(value, packedValues) { switch (typeof value) { case "string": @@ -1075,7 +1113,7 @@ function typedArrayEncoder(tag, size) { tag, encode: function(typedArray, encode) { let length = typedArray.byteLength, offset = typedArray.byteOffset || 0, buffer = typedArray.buffer || typedArray; - encode(hasNodeBuffer ? Buffer.from(buffer, offset, length) : new Uint8Array(buffer, offset, length)); + encode(hasNodeBuffer ? Buffer$1.from(buffer, offset, length) : new Uint8Array(buffer, offset, length)); } }; } diff --git a/delivery/common/cwt/lib/cwt.d.ts b/delivery/common/cwt/lib/cwt.d.ts index dbadee73..80e29344 100644 --- a/delivery/common/cwt/lib/cwt.d.ts +++ b/delivery/common/cwt/lib/cwt.d.ts @@ -1,4 +1,4 @@ -/** @preserve @version 1.1.0 */ +/** @preserve @version 1.2.0 */ /** * Claim validation depends on integer label in the CWT claims set. @@ -7,7 +7,8 @@ */ declare type CWTOptions = { /** - * If CwtTag is prepended to the CWT token, we will verify the same. Default is set to false + * If CwtTag is prepended to the CWT token, we will verify the same. Default is set to false. + * During CWT generation, this flag can be used to add CWT tag for the token */ isCWTTagAdded?: boolean; /** @@ -16,6 +17,7 @@ declare type CWTOptions = { defaultCoseMsgType?: number; /** * If COSE CBOR is added to CWT token, Default is set to true. + * During CWT generation, this flag can be used to add COSE tag for the token */ isCoseCborTagAdded?: boolean; /** @@ -60,6 +62,34 @@ declare type CWTJSON = { }; payload: unknown; }; +/** + * Wrapper class to hold protected / unprotected header claims. + */ +declare type ContentHeader = { + p?: Map; + u?: Map; +}; +declare type Signer = { + key: CryptoKey; + p?: Map; + u?: Map; + externalAAD?: Uint8Array; +}; +declare type Verifier = { + key: CryptoKey; + externalAAD?: Uint8Array; + kid?: Uint8Array; +}; + +declare const HeaderLabels: { + [key: string]: number; +}; +declare const ClaimLabels: { + [key: string]: number; +}; +declare const AlgorithmLabels: { + [key: string]: number; +}; /** * CWTUtil contains helper APIs for converting base64/hex encoded string to binary and translating CWT payload/claims. @@ -73,21 +103,24 @@ declare class CWTUtil { */ static toHexString(byteArray: Uint8Array): string; /** - * Translates integer keys from the payload to the string keys from the labelsMap mapping. - * The value is passed to the traslator function before assigning to the keys. - * @param payload A JSON/Map object where keys are integer. @example { 1: 'john@issuer', 2: 'akamai@subject', 7: Uint8Array([72, 83, 50, 53, 54])} - * @param labelsMap A JSON object containing the mapping of integer keys used in CWT payload to string keys. @example { 1: 'iss', 2: 'sub', 3: 'aud', 4: 'exp', 5: 'nbf', 6: 'iat', 7: 'cti' } + * Translates keys of the map to the keys matched from the labelsMap. The value of the key from the paylaod is passed to the traslator function before assigning to the keys. + * @param payload A JSON/Map object where keys are integer or string. @example { 1: 'john@issuer', 2: 'akamai@subject', 7: Uint8Array([72, 83, 50, 53, 54])} + * @param labelsMap A JSON object containing the mapping of integer/string keys used in CWT payload to string/integer keys. @example { 1: 'iss', 2: 'sub', 3: 'aud', 4: 'exp', 5: 'nbf', 6: 'iat', 7: 'cti' } * @param tranlators A JSON object containing the mapping of field with the translator function. @example { cti: (input: Uint8Array) => TextDecoder().decode(input)} - * @returns Instance of JSON object where keys are string. @example { 'iss': 'john@issuer', 'sub': akamai@subject, 'cti': 'HS256' } + * @returns Instance of JSON object where keys are string or number. @example { 'iss': 'john@issuer', 'sub': akamai@subject, 'cti': 'HS256' } */ static claimsTranslate(payload: any, labelsMap: { - [key: number | string]: string; + [key: number | string]: string | number; }, translators?: { [key: string]: Function; - }): { - [key: string]: unknown; - [key: number]: unknown; - }; + }): Map; + /** + * Helper method to compare two Uint8Array + * @param arr1 Instance of Uint8Array + * @param arr2 Instance of Uint8Array + * @returns Boolean indicating result of comparsion + */ + static isUint8ArrayEqual(arr1: Uint8Array, arr2: Uint8Array): boolean; } /** @@ -111,20 +144,19 @@ declare class CWTValidator { * Decodes and performs signature validation one the CWT token. * It also validates the CWT payload claims and headers if validation is enabled via ${@link cwtOptions}. * @param tokenBuf CWT token in binary format (i.e Uint8Array) - * @param keys List of {@link CryptoKey } to be used for signature verification. Token is considered valid if signature is verifiable by any one key. - * @param externalAAD Externally supplied data in binary (i.e Uint8Array) that needs to be authenticated which is not carried as part of the COSE message. + * @param verifiers List of {@link Verifier } to be used for signature verification. Token is considered valid if signature is verifiable by any one key. Incase of multi signature cose structure, key matching the kid will be used for verification. * @returns Instance of {@link CWTJSON } containing header and payload * @throws {[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)} with appropriate message if type checks fails for arguments. * @throws {[DOMException](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) | [TypeError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError)} when trying to use an invalid key data or when the key is not a key for the algorithm or when trying to use an algorithm that is either unknown or isn't suitable for a verify operation. * @example Error('Invalid arguments!') - If arguments types are invalid. * @example Error('CWT malformed: expected CWT CBOR tag for token!') - If cwtOptions.isCWTTagAdded is enabled but CWT token generated does not have CWTTag(61) added. */ - validate(tokenBuf: Uint8Array, keys: CryptoKey[], externalAAD?: Uint8Array): Promise; + validate(tokenBuf: Uint8Array, verifiers: Verifier[]): Promise; /** * Process COSE message structure and performs header validation if enabled. see {@link https://datatracker.ietf.org/doc/rfc8152/ } for COSE message spec used to generate CWT tokens. * @param coseMessage Array of cbor encode messages. * @param cwtType COSE message tag. - * @param keys List of keys in binary (i.e Uint8Array) used for verifying CWT token. + * @param verifiers List of {@link Verifier } to be used for signature verification. Token is considered valid if signature is verifiable by any one key. Incase of multi signature cose structure, key matching the kid will be used for verification. * @param headerValidation Boolean to enable/disable validation on header fields. * @param externalAAD Externally supplied data in binary (i.e Uint8Array) that needs to be authenticated which is not carried as part of the COSE message. * @returns Promise of {@link CWTJSON}. @@ -150,6 +182,40 @@ declare class CWTValidator { * @throws {[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)} if type check validation fails on {@link cwtOptions} fields. */ private validateOptionTypes; + /** + * Returns verifier whose key will be used to perform signature verification + * @param signU Unprotected header from Cose_Signature message structure + * @param verifiers List of verifiers with verifying key and kid. + * @returns Verifier whose kid matches the kid from signU (i.e unprotected header from Cose_Signature) + */ + private getVerifier; +} +/** + * CWTGenerator modules provides APIs to create CWT token. + * The module follows CWT tokens generated using spec defined here {@link https://www.rfc-editor.org/rfc/rfc8392.html}. + * As of now the module only supports COSE_Sign,COSE_Sign1, COSE_MAC0 and COSE_MAC structure. Also, Only ES256 and HS256 algorithms are supported at the moment. + * + */ +declare class CWTGenerator { + /** + * + * @param claims JSON object containing CWT claims set + * @param signer Instance of {@link Signer} which will used to sign the CWT token. One can pass external data to be included during signature computation. + * @param contentHeader JSON object containing protected and unprotected header fields. see {@link ContentHeader} for more details. + * @param recipients Recipients to which the signingKey needs to be shared with. (Note: currently not supported) + * @param options CWT options. see {@link CWTOptions} for more details. Only {@link CWTOptions.isCWTTagAdded} and {@link CWTOptions.isCoseCborTagAdded} are relevant during CWT token creation. + * @returns CWT token in {@link Uint8Array} + */ + static mac(claims: Map, signer: Signer, contentHeader: ContentHeader, recipients?: Signer[], options?: CWTOptions): Promise; + /** + * Generates CWT token based on assymetric cryptography scheme. + * @param claims JSON object containing CWT claims set + * @param signers Single or multiple signer(s) that will be used to sign the CWT token. One can pass external data for each signger that needs to be included during signature computation. If passed as array, COSE_Sign message structure is used or else COSE_Sign1 + * @param contentHeader JSON object containing protected and unprotected header fields. see {@link ContentHeader} for more details. + * @param options CWT options. see {@link CWTOptions} for more details. Only {@link CWTOptions.isCWTTagAdded} and {@link CWTOptions.isCoseCborTagAdded} are relevant during CWT token creation. + * @returns CWT token in {@link Uint8Array} + */ + static sign(claims: any, signers: Signer | Array, contentHeader?: ContentHeader, options?: CWTOptions): Promise; } -export { CWTJSON, CWTUtil, CWTValidator }; +export { AlgorithmLabels, CWTGenerator, CWTJSON, CWTUtil, CWTValidator, ClaimLabels, HeaderLabels, Signer, Verifier }; diff --git a/delivery/common/cwt/lib/cwt.js b/delivery/common/cwt/lib/cwt.js index caafead0..a207206b 100644 --- a/delivery/common/cwt/lib/cwt.js +++ b/delivery/common/cwt/lib/cwt.js @@ -1,21 +1,47 @@ -/** @preserve @version 1.1.1 */ +/** @preserve @version 1.2.0 */ import { crypto } from "crypto"; -import { Encoder, Decoder } from "./cbor-x.js"; +import { Encoder, Decoder, Tag } from "./cbor-x.js"; + +import { logger } from "log"; const COSE_Mac0 = 17, COSE_Mac = 97, COSE_Sign = 98, COSE_Sign1 = 18, COSE_Encrypt0 = 16, COSE_Encrypt = 96, coseAlgTags = { - 5: "HMAC 256/256", - "-7": "ES256" -}, HeaderLabelToKey_alg = 1, HeaderLabelToKey_crit = 2, claimsLabelToKey_iss = 1, claimsLabelToKey_sub = 2, claimsLabelToKey_aud = 3, claimsLabelToKey_exp = 4, claimsLabelToKey_nbf = 5; + "-7": "ES256", + 5: "HS256", + "-37": "PS256" +}, HeaderLabels = { + alg: 1, + crit: 2, + kid: 4 +}, ClaimLabels = { + iss: 1, + sub: 2, + aud: 3, + exp: 4, + nbf: 5, + iat: 6, + cti: 7 +}, AlgorithmLabels = { + ES256: -7, + HS256: 5, + PS256: -37 +}; class Mac { - static async verifyHMAC(alg, message, signature, keys) { - if ("HMAC 256/256" === alg) { - let isSignVerified = !1; - for (const key of keys) if (isSignVerified = await crypto.subtle.verify({ - name: "HMAC" - }, key, signature, message), isSignVerified) return Promise.resolve(isSignVerified); - return Promise.resolve(isSignVerified); + static async verifyHMAC(alg, message, signature, key) { + if ("HS256" === alg) return await crypto.subtle.verify({ + name: "HMAC" + }, key, signature, message); + throw new Error(`Unsupported Algorithm, ${alg}`); + } + static async doSign(signaturePayload, key, alg) { + if ("HS256" === alg) { + return await crypto.subtle.sign({ + name: "HMAC", + hash: { + name: "SHA-256" + } + }, key, signaturePayload); } throw new Error(`Unsupported Algorithm, ${alg}`); } @@ -28,28 +54,68 @@ class CWTUtil { })).join(""); } static claimsTranslate(payload, labelsMap, translators) { - const result = {}; - for (const param in payload) { - const key = labelsMap[param] ? labelsMap[param] : param, theValue = translators && translators[key] ? translators[key](payload[param]) : payload[param]; - result[key] = theValue; + if (payload instanceof Map) { + const result = new Map; + for (const [k, v] of payload) { + const tK = labelsMap[k] ? labelsMap[k] : k, tV = translators && translators[tK] ? translators[tK](v) : v; + result.set(tK, tV); + } + return result; } - return result; + { + const result = new Map; + for (const param in payload) { + const key = labelsMap[param] ? labelsMap[param] : param, theValue = translators && translators[key] ? translators[key](payload[param]) : payload[param]; + result.set(key, theValue); + } + return result; + } + } + static isUint8ArrayEqual(arr1, arr2) { + return arr1 instanceof Uint8Array && (arr2 instanceof Uint8Array && (arr1.length === arr2.length && arr1.every(((value, index) => value === arr2[index])))); } } CWTUtil.EMPTY_BUFFER = new Uint8Array(0); class Sign { - static async verifySignature(alg, message, signature, keys) { - if ("ES256" === alg) { - let isSignVerified = !1; - for (const key of keys) if (isSignVerified = await crypto.subtle.verify({ + static async verifySignature(alg, message, signature, key) { + switch (alg) { + case "ES256": + return await crypto.subtle.verify({ name: "ECDSA", hash: "SHA-256" - }, key, signature, new Uint8Array(message)), isSignVerified) return Promise.resolve(isSignVerified); - return Promise.resolve(isSignVerified); + }, key, signature, new Uint8Array(message)); + + case "PS256": + return await crypto.subtle.verify({ + name: "RSA-PSS", + saltLength: 32 + }, key, signature, new Uint8Array(message)); + + default: + throw new Error(`Unsupported Algorithm, ${alg}`); + } + } + static async doSign(signaturePayload, key, alg) { + switch (alg) { + case "ES256": + return await crypto.subtle.sign({ + name: "ECDSA", + hash: { + name: "SHA-256" + } + }, key, signaturePayload); + + case "PS256": + return await crypto.subtle.sign({ + name: "RSA-PSS", + saltLength: 32 + }, key, signaturePayload); + + default: + throw new Error(`Unsupported Algorithm, ${alg}`); } - throw new Error(`Unsupported Algorithm, ${alg}`); } } @@ -61,10 +127,9 @@ class CWTValidator { constructor(cwtOptions) { this.cwtOptions = cwtOptions || {}, this.validateOptionTypes(); } - async validate(tokenBuf, keys, externalAAD) { + async validate(tokenBuf, verifiers) { if (!(tokenBuf instanceof Uint8Array)) throw new Error("Invalid token type, expected Uint8Array!"); - if (externalAAD && !(externalAAD instanceof Uint8Array)) throw new Error("Invalid externalAAD type, expected Uint8Array!"); - if (!Array.isArray(keys) || !keys.every((elem => void 0 !== elem.type || void 0 !== elem.extractable || null != elem.algorithm || null != elem.usages))) throw new Error("Invalid keys type, expected list of CryptoKey!"); + if (verifiers.find((elem => void 0 === elem.key || void 0 === elem.key.type || null == elem.key.algorithm || null == elem.key.usages || elem.externalAAD && !(elem.externalAAD instanceof Uint8Array) || elem.kid && !(elem.kid instanceof Uint8Array)))) throw new Error("Invalid verifiers, expected list of verifier with valid crypto key!"); let coseMessage = globalThis.cbordec.decode(tokenBuf), cwtType = this.cwtOptions.defaultCoseMsgType; if (this.cwtOptions.isCWTTagAdded) { if (61 !== coseMessage.tag) throw new Error("CWT malformed: expected CWT CBOR tag for the token!"); @@ -74,27 +139,31 @@ class CWTValidator { if (cwtType = coseMessage.tag, ![ COSE_Mac0, COSE_Mac, COSE_Sign, COSE_Sign1, COSE_Encrypt, COSE_Encrypt0 ].includes(cwtType)) throw new Error("CWT malformed: invalid COSE CBOR tag!"); coseMessage = coseMessage.value; } - externalAAD || (externalAAD = CWTUtil.EMPTY_BUFFER); - const cwtJSON = await this.verifyCoseMessage(coseMessage, cwtType, keys, this.cwtOptions.headerValidation, externalAAD); + const cwtJSON = await this.verifyCoseMessage(coseMessage, cwtType, verifiers, this.cwtOptions.headerValidation); return this.validateClaims(cwtJSON.payload), cwtJSON; } - async verifyCoseMessage(coseMessage, cwtType, keys, headerValidation, externalAAD) { + async verifyCoseMessage(coseMessage, cwtType, verifiers, headerValidation) { switch (cwtType) { case COSE_Mac0: { if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR MAC0Tag, expected array of length 4!"); const [p, u, payload, tag] = coseMessage; let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; - pH = pH.size ? pH : CWTUtil.EMPTY_BUFFER; const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; let alg; - if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabelToKey_alg); else { + if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabels.alg); else { if (uH === CWTUtil.EMPTY_BUFFER) throw new Error("CWT malformed: unable to find algo field from CWT token."); - alg = u.get(HeaderLabelToKey_alg); + alg = uH.get(HeaderLabels.alg); } - alg = coseAlgTags[alg.toString()]; - const MACstructure = [ "MAC0", p, externalAAD, payload ], toBeMACed = globalThis.cborenc.encode(MACstructure); - if (!await Mac.verifyHMAC(alg, toBeMACed, tag, keys)) throw new Error("CWT token signature verification failed!"); + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER; + alg = coseAlgTags[alg]; + let isSignVerified = !1; + for (const verifier of verifiers) { + const MACstructure = [ "MAC0", pP, verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, payload ], toBeVerified = globalThis.cborenc.encode(MACstructure); + if (isSignVerified = await Mac.verifyHMAC(alg, toBeVerified, tag, verifier.key), + isSignVerified) break; + } + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); const decodedPayload = globalThis.cbordec.decode(payload); return Promise.resolve({ header: { @@ -108,18 +177,57 @@ class CWTValidator { case COSE_Sign1: { if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR COSE_Sign1, expected array of length 4!"); - const [p, u, payload, signer] = coseMessage; + const [p, u, payload, signature] = coseMessage; let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; pH = pH.size ? pH : CWTUtil.EMPTY_BUFFER; const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; let alg; - if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabelToKey_alg); else { + if (headerValidation && this.validateHeader(pH, !0), pH !== CWTUtil.EMPTY_BUFFER) alg = pH.get(HeaderLabels.alg); else { if (uH === CWTUtil.EMPTY_BUFFER) throw new Error("CWT malformed: unable to find algo field from CWT token."); - alg = u.get(HeaderLabelToKey_alg); + alg = u.get(HeaderLabels.alg); + } + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER; + alg = coseAlgTags[alg]; + let isSignVerified = !1; + for (const verifier of verifiers) { + const SigStructure = [ "Signature1", pP, verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, payload ], toBeVerified = globalThis.cborenc.encode(SigStructure); + if (isSignVerified = await Sign.verifySignature(alg, toBeVerified, signature, verifier.key), + isSignVerified) break; } - alg = coseAlgTags[alg.toString()]; - const SigStructure = [ "Signature1", p, externalAAD, payload ], toBeVeried = globalThis.cborenc.encode(SigStructure); - if (!await Sign.verifySignature(alg, toBeVeried, signer, keys)) throw new Error("CWT token signature verification failed!"); + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); + const decodedPayload = globalThis.cbordec.decode(payload); + return Promise.resolve({ + header: { + p: pH, + u: uH + }, + payload: decodedPayload + }); + } + + case COSE_Sign: + { + if (!Array.isArray(coseMessage) || 4 !== coseMessage.length) throw new Error("CWT malformed: invalid COSE message structure for COSE CBOR COSE_Sign, expected array of length 4!"); + const [p, u, payload, signatures] = coseMessage; + let pH = p.length ? globalThis.cbordec.decode(p) : CWTUtil.EMPTY_BUFFER; + const uH = u.size ? u : CWTUtil.EMPTY_BUFFER; + headerValidation && this.validateHeader(pH, !0); + let pP = pH.size ? p : CWTUtil.EMPTY_BUFFER, isSignVerified = !1; + for (const signature of signatures) { + let [signP, signU, sign] = signature; + const verifier = this.getVerifier(signU, verifiers); + if (verifier) { + const externalAAD = verifier.externalAAD ? verifier.externalAAD : CWTUtil.EMPTY_BUFFER, signerPMap = signP.length ? globalThis.cborenc.decode(signP) : CWTUtil.EMPTY_BUFFER; + signP = signerPMap.size ? signP : CWTUtil.EMPTY_BUFFER; + const alg = signerPMap.get ? signerPMap.get(HeaderLabels.alg) : pH.get ? pH.get(HeaderLabels.alg) : void 0; + if (alg) { + const SigStructure = [ "Signature", pP, signP, externalAAD, payload ], toBeVerified = globalThis.cborenc.encode(SigStructure); + if (isSignVerified = await Sign.verifySignature(coseAlgTags[alg], toBeVerified, sign, verifier.key), + isSignVerified) break; + } else logger.error(`Unable to find alg in CWT token for kid = ${verifier.kid}, hence skipping the verifier!`); + } + } + if (!isSignVerified) throw new Error("CWT token signature verification failed!"); const decodedPayload = globalThis.cbordec.decode(payload); return Promise.resolve({ header: { @@ -136,7 +244,7 @@ class CWTValidator { } validateHeader(headers, pheader) { if (headers.size && pheader) { - const h = headers, crit = h.get(HeaderLabelToKey_crit); + const h = headers, crit = h.get(HeaderLabels.crit); if (Array.isArray(crit)) { if (0 === crit.length) throw new Error("CWT Malformed: malformed protected header, crit array cannot be empty!"); for (const e of crit) if (void 0 === h.get(e)) throw new Error("CWT Malformed: malformed protected header, crit labels are not part of protected header"); @@ -145,29 +253,29 @@ class CWTValidator { } validateClaims(decodedPayload) { if (this.cwtOptions.issuer) { - const iss = decodedPayload.get(claimsLabelToKey_iss); + const iss = decodedPayload.get(ClaimLabels.iss); if (iss && this.cwtOptions.issuer !== iss) throw new Error(`CWT malformed: invalid iss, expected ${this.cwtOptions.issuer}`); } if (this.cwtOptions.subject) { - const sub = decodedPayload.get(claimsLabelToKey_sub); + const sub = decodedPayload.get(ClaimLabels.sub); if (sub && this.cwtOptions.subject !== sub) throw new Error(`CWT malformed: invalid sub, expected ${this.cwtOptions.subject}`); } if (this.cwtOptions.audience) { - const aud = decodedPayload.get(claimsLabelToKey_aud); + const aud = decodedPayload.get(ClaimLabels.aud); if (aud && (Array.isArray(aud) || "string" == typeof aud)) { if (!(Array.isArray(aud) ? aud : [ aud ]).includes(this.cwtOptions.audience)) throw new Error(`CWT malformed: invalid aud, expected ${this.cwtOptions.audience}`); } } const clockTimestamp = Math.floor(Date.now() / 1e3); if (!1 === this.cwtOptions.ignoreExpiration) { - const exp = decodedPayload.get(claimsLabelToKey_exp); + const exp = decodedPayload.get(ClaimLabels.exp); if (exp) { if ("number" != typeof exp) throw new Error("CWT malformed: exp must be number"); if (clockTimestamp > exp + (this.cwtOptions.clockTolerance || 0)) throw new Error("CWT token has been expired"); } } if (!1 === this.cwtOptions.ignoreNotBefore) { - const nbf = decodedPayload.get(claimsLabelToKey_nbf); + const nbf = decodedPayload.get(ClaimLabels.nbf); if (nbf) { if ("number" != typeof nbf) throw new Error("CWT malformed: nbf must be number"); if (nbf > clockTimestamp + (this.cwtOptions.clockTolerance || 0)) throw new Error("CWT is not active"); @@ -187,6 +295,66 @@ class CWTValidator { if (void 0 !== this.cwtOptions.clockTolerance && "number" != typeof this.cwtOptions.clockTolerance) throw new Error("Invalid cwtOptions: clockTolerance must be number"); this.cwtOptions.clockTolerance = 60; } + getVerifier(signU, verifiers) { + const kid = signU.get(HeaderLabels.kid); + for (const verifier of verifiers) if (CWTUtil.isUint8ArrayEqual(kid, verifier.kid)) return verifier; + return null; + } +} + +class CWTGenerator { + static async mac(claims, signer, contentHeader, recipients, options) { + if (!claims) throw new Error("Invalid claims type, cannot be null or undefined!"); + if (void 0 === signer.key || void 0 === signer.key.type || null == signer.key.algorithm || null == signer.key.usages || signer.externalAAD && !(signer.externalAAD instanceof Uint8Array)) throw new Error("Invalid signer, expected signer with valid crypto key!"); + if (!contentHeader) throw new Error("Invalid contentHeader type, cannot be null or undefined!"); + options = options || {}; + let pH = contentHeader && contentHeader.p ? contentHeader.p : new Map, uH = contentHeader && contentHeader.u ? contentHeader.u : new Map, protectedHeader = 0 === pH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(pH), alg = pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader!"); + let result, payload = globalThis.cborenc.encode(claims); + if (Array.isArray(recipients)) { + if (0 === recipients.length) throw new Error("No recipients found, there has to be atleast one recipients!"); + if (recipients.length > 0) throw new Error("Mac with recipients is not currently supported!"); + } else { + const MACstructure = [ "MAC0", protectedHeader, signer.externalAAD || CWTUtil.EMPTY_BUFFER, payload ]; + let signed = globalThis.cborenc.encode(MACstructure); + result = [ protectedHeader, uH, payload, await Mac.doSign(signed, signer.key, coseAlgTags[alg]) ], + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(result, COSE_Mac0) : result; + } + return result = options.isCWTTagAdded ? new Tag(result, 61) : result, globalThis.cborenc.encode(result); + } + static async sign(claims, signers, contentHeader, options) { + if (!claims) throw new Error("Invalid claims type, cannot be null or undefined!"); + if (!signers || Array.isArray(signers) && 0 == signers.length) throw new Error("Invalid signers, cannot be null or undefined, requies at atleast one signer!"); + if (Array.isArray(signers)) { + if (signers.find((elem => void 0 === elem.key || void 0 === elem.key.type || null == elem.key.algorithm || null == elem.key.usages || elem.externalAAD && !(elem.externalAAD instanceof Uint8Array)))) throw new Error("Invalid signers, expected list of signers with valid crypto key!"); + } else if (void 0 === signers.key || void 0 === signers.key.type || null == signers.key.algorithm || null == signers.key.usages || signers.externalAAD && !(signers.externalAAD instanceof Uint8Array)) throw new Error("Invalid signer, expected signer with valid crypto key!"); + options = options || {}; + let result, pH = contentHeader && contentHeader.p ? contentHeader.p : new Map, uH = contentHeader && contentHeader.u ? contentHeader.u : new Map, protectedHeader = 0 === pH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(pH), payload = globalThis.cborenc.encode(claims); + if (Array.isArray(signers)) { + let signatures = Array(); + for (const signer of signers) { + const externalAAD = signer.externalAAD || CWTUtil.EMPTY_BUFFER; + let signPH = signer.p ? signer.p : new Map, signUH = signer.u ? signer.u : new Map, alg = signPH.get(HeaderLabels.alg) || pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader or Signer object!"); + let signprotectedHeader = 0 === signPH.size ? CWTUtil.EMPTY_BUFFER : globalThis.cborenc.encode(signPH); + const SigStructure = [ "Signature", protectedHeader, signprotectedHeader, externalAAD, payload ]; + let signaturePayload = globalThis.cborenc.encode(SigStructure); + const sig = await Sign.doSign(signaturePayload, signer.key, coseAlgTags[alg]); + signatures.push([ signprotectedHeader, signUH, sig ]); + } + let signed = [ protectedHeader, uH, payload, signatures ]; + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(signed, COSE_Sign) : signed; + } else { + const externalAAD = signers.externalAAD || CWTUtil.EMPTY_BUFFER; + let alg = pH.get(HeaderLabels.alg); + if (!alg) throw new Error("No algorithm found, kindly specify the algorithm in protected header of ContentHeader!"); + const SigStructure = [ "Signature1", protectedHeader, externalAAD, payload ]; + let signaturePayload = globalThis.cborenc.encode(SigStructure); + let signed = [ protectedHeader, uH, payload, await Sign.doSign(signaturePayload, signers.key, coseAlgTags[alg]) ]; + result = options.isCoseCborTagAdded || void 0 === options.isCoseCborTagAdded ? new Tag(signed, COSE_Sign1) : signed; + } + return result = options.isCWTTagAdded ? new Tag(result, 61) : result, globalThis.cborenc.encode(result); + } } -export { CWTUtil, CWTValidator }; +export { AlgorithmLabels, CWTGenerator, CWTUtil, CWTValidator, ClaimLabels, HeaderLabels };