You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Would it make sense for structured-headers to support a raw data type which is meant to represent pre-serialized data? This would be somewhat similar to JSON.rawJSON() except for structured-headers (but with key differences; see the Security notes below).
Description
For example, suppose you are generating an HTTP signature using structured-headers directly. Doing so requires serializing the same Inner List value twice:
In the above code snippet, signatureParameters is first serialized as a standalone Inner List, and then serialized again as an Inner List enclosed in a Dictionary.
If structured-headers had a way to represent pre-serialized values, this double-serialization could be eliminated by explicitly passing a "raw" value:
(Of course, in this specific example, it is possible to simply prepend "sig=" to serializedSignatureParameters, but that would be side-stepping the serialization provided by structured-headers.)
What do you think?
Workaround
It is actually possible to do this today with a hacky workaround:
classRawValueextendsstructuredHeaders.Token{constructor(value){// `Token` strictly validates its value, so pass a dummy string...super("a");// ...and then reassign the actual value.this.value=value;}}constserializedSignatureParameters=structuredHeaders.serializeInnerList(signatureParameters);constsignatureInput=structuredHeaders.serializeDictionary({sig: newRawValue(serializedSignatureParameters),});
Additional information
The above code snippet was extracted from a larger example of producing HTTP signatures, which is included below for completeness:
importcryptofrom"node:crypto";import*asstructuredHeadersfrom"structured-headers";constbody="Hello, world!";consttargetUri=newURL("https://example.com/foo?param=value&pet=dog");constsignatureComponents={"@method": "GET","@target-uri": targetUri.href,};constsignatureParameters=[Object.keys(signatureComponents),newMap([["alg","hmac-sha256"],["created",Math.floor(newDate()/1_000)],]),];constsignatureLabel="sig";// NOTE: This is for illustration purposes only, and glosses over complexities// of generating the signature base.constsignatureBase=Object.entries({
...signatureComponents,"@signature-params":
structuredHeaders.serializeInnerList(signatureParameters),}).map(([name,value])=>`${structuredHeaders.serializeItem(name)}: ${value}`).join("\n");constrequestTarget=targetUri.href.slice(targetUri.origin.length);constsignature=crypto.createHmac("sha256","secret").update(signatureBase).digest();constheaders={Host: targetUri.host,"Signature-Input": structuredHeaders.serializeDictionary({[signatureLabel]: signatureParameters,}),Signature: structuredHeaders.serializeDictionary({[signatureLabel]: signature.buffer,}),};console.log(`${signatureComponents["@method"]}${requestTarget} HTTP/1.1`);for(const[header,value]ofObject.entries(headers)){console.log(`${header}: ${value}`);}console.log(`\n${body}`);
Security notes
JSON.rawJSON() was used as an inspiration for this request, but its implementation differs in some key ways:
JSON.rawJSON() does not support creation of objects and arrays.
Presumably, this is to reduce the severity of security issues around untrusted input being passed to JSON.rawJSON(). However, without seeing discussion notes leading up this implementation decision, I can't say for sure.
It may be useful for this proposed structuredHeaders.RawValue type to likewise prohibit Lists and Dictionaries, but allow representation of all other types. This is because Lists and Dictionaries can only appear at the top level, so neither type can be enclosed within a parent:
constserializedDictionary=structuredHeaders.serializeDictionary({a: 1,b: 2,});structuredHeaders.serializeList([3,// INVALID! Lists and Dictionary types can only appear at the top level.newstructuredHeaders.RawValue(serializedDictionary),]);
For both of the above points, it would be required to parse/validate the raw string passed to structuredHeaders.RawValue, which would have a performance impact. Since this issue wasn't long enough already, 😄 I wrote up a potential alternative below:
I may be going down a rabbit hole here, but one idea to work around this trade-off is to limit structuredHeaders.RawValue instances to only originate from dedicated serialization functions. For example, these functions could be made available under structuredHeaders.raw to not pollute the top-level package namespace:
// structured-headers/src/raw.jsconstrawValue=Symbol("rawValue");classRawValue{// Allow extraction of raw value string. Not having this limits the utility of// `RawValue` .toString(){returnthis[rawValue];}}functioncreateRawValue(value){returnObject.assign(newRawValue(),{[rawValue]: value});}exportfunctionserializeItem(input,params){returncreateRawValue(structuredHeaders.serializeItem(input,params));}// NOTE: `serializeDictionary` and `serializeList` are intentionally omitted per (1) above.//// Same implementation for the rest://// export function serializeInnerList ...// export function serializeItem ...// export function serializeInnerList ...// export function serializeBareItem ...// export function serializeInteger ...// export function serializeDecimal ...// export function serializeString ...// export function serializeDisplayString ...// export function serializeBoolean ...// export function serializeByteSequence ...// export function serializeToken ...// export function serializeDate ...// export function serializeParameters ...// export function serializeKey ...
Summary
Would it make sense for
structured-headers
to support a raw data type which is meant to represent pre-serialized data? This would be somewhat similar toJSON.rawJSON()
except forstructured-headers
(but with key differences; see the Security notes below).Description
For example, suppose you are generating an HTTP signature using
structured-headers
directly. Doing so requires serializing the same Inner List value twice:In the above code snippet,
signatureParameters
is first serialized as a standalone Inner List, and then serialized again as an Inner List enclosed in a Dictionary.If
structured-headers
had a way to represent pre-serialized values, this double-serialization could be eliminated by explicitly passing a "raw" value:(Of course, in this specific example, it is possible to simply prepend
"sig="
toserializedSignatureParameters
, but that would be side-stepping the serialization provided bystructured-headers
.)What do you think?
Workaround
It is actually possible to do this today with a hacky workaround:
Additional information
The above code snippet was extracted from a larger example of producing HTTP signatures, which is included below for completeness:
Security notes
JSON.rawJSON()
was used as an inspiration for this request, but its implementation differs in some key ways:JSON.rawJSON()
does not support creation of objects and arrays.Presumably, this is to reduce the severity of security issues around untrusted input being passed to
JSON.rawJSON()
. However, without seeing discussion notes leading up this implementation decision, I can't say for sure.It may be useful for this proposed
structuredHeaders.RawValue
type to likewise prohibit Lists and Dictionaries, but allow representation of all other types. This is because Lists and Dictionaries can only appear at the top level, so neither type can be enclosed within a parent:JSON.rawJSON()
rejects malformed JSON.Likewise,
structuredHeaders.RawValue
may reject malformed input.Warning
For both of the above points, it would be required to parse/validate the raw string passed to
structuredHeaders.RawValue
, which would have a performance impact. Since this issue wasn't long enough already, 😄 I wrote up a potential alternative below:I may be going down a rabbit hole here, but one idea to work around this trade-off is to limit
structuredHeaders.RawValue
instances to only originate from dedicated serialization functions. For example, these functions could be made available understructuredHeaders.raw
to not pollute the top-level package namespace:The text was updated successfully, but these errors were encountered: