An ambiguos part of DKIM specification about header hash - email

I'm trying to sign email messages according to DKIM specification. I'm reading RFC 6376 and see there relatively straight way to create the signature, but there is a problematic part about DKIM-Signature header's tag b.
As I understand, it's value should be a base64 encoded signature of a hash, that is calculated against selected message headers, including DKIM-Signature, but without a value of the tag b. If it's correct, then I have some trouble understanding RFC here:
The header field MUST be presented to the hash algorithm after the
body of the message rather than with the rest of the header fields
Here the RFC speaks about DKIM-Signature header. The text is from the chapter "3.7. Computing the Message Hashes".
If I understand it correctly, then it means I must calculate a hash of a block, which includes full message body, with the DKIM-Signature header appended to it's end (variant 1). But as I read in other sources, including answers here, it seems (because there's no clear algorithm in other sources) that DKIM-Signature header should be appended to the end of a list of message headers, that are selected for the hashing (variant 2). Then the ambiguity is: which variant is correct?
And finally, after 2 hashes are calculated, should I sign the second hash (hash of headers after disambiguation of a correct variant), as it seems things should work, or should I sign just the list of selected (and canonicalized) headers?
A common sense tells me that variant 2 should be the actual choice, and signature should be applied to the second hash, but I'm in doubts.

The pseudo-code for the signature algorithm in RFC 6376 is wrong. It was corrected in Errata 5252:
body-hash = hash-alg (canon-body, l-param)
data-hash = hash-alg (h-headers, D-SIG)
signature = sig-alg (d-domain, selector, data-hash)
What it means is that you first hash the canonicalized message body up to the length specified in the l parameter and then include this hash in the bh tag of the DKIM-Signature header field. Afterwards, you start a new hash with the header fields as specified in the h tag and canonicalized according to the c tag (an empty string is included for non-existent header fields, so that adding these header fields later on invalidates the DKIM signature) followed by the DKIM-Signature header field which you are about to add without the b tag. The resulting hash is the one you sign. This means that your second variant is the correct one.

Related

Can one REGISTER request contain multiple To Header Fields and what would be the scenarios?

Can we have multiple "To" header in any SIP request/response?
In rfc3261, defining SIP, multiple To headers are not allowed.
The interesting part is from Section 7.3 Header Fields
SIP header fields are similar to HTTP header fields in both syntax
and semantics. In particular, SIP header fields follow the [H4.2]
definitions of syntax for the message-header and the rules for
extending header fields over multiple lines. However, the latter is
specified in HTTP with implicit whitespace and folding. This
specification conforms to RFC 2234 [10] and uses only explicit
whitespace and folding as an integral part of the grammar.
[H4.2] also specifies that multiple header fields of the same field
name whose value is a comma-separated list can be combined into one
header field. That applies to SIP as well, but the specific rule is
different because of the different grammars. Specifically, any SIP
header whose grammar is of the form
header = "header-name" HCOLON header-value *(COMMA header-value)
Because the To header Augmented BNF does not contains COMMA, it means To can only be added once:
To = ( "To" / "t" ) HCOLON ( name-addr
/ addr-spec ) *( SEMI to-param )
The full Augmented BNF, which describe the rules, is available in Section 25

Email messages: header section of an email-message, email-message envelope, email-message body and SMTP

Please, let me know whether my understanding of these things is correct:
Email-message consits of two parts: email-message envelope and contents of an email-message.
Email-message envelope is an information that is formed step by step during
SMTP-handhaking phases on a path of a message from sender's mailbox
to reciever's mailbox (during exchanging command-reply pairs between each
SMTP-client and SMTP-server on a path).
Header section is one of the two parts of a content of an email-message
(along with message body), and initially consists of the lines written by sender along with the body of the message in his email client.
Body of the email message - this thing is self-explanatory.
RFC 5322 specifies the format of the email-message content (header section and message body), and RFC 5321 specifies the work of the SMTP protocol.
Although header section of the email-message is initially formed by sender of email-message along with the body of the message (in his email-client), this header section may be further extended with some header fields containing envelope information during the path of the email-message through different SMTP-servers. For example, SMTP-server can append a new "Received:" header line to the header section. These modifications of header section must be performed according to the rules from RFC 5321 and after each of these modifications the resulting header section must me consistent with the format specified in RFC 5322.
When we open a received email-message with an email-client GUI, we see only the message body and a part of the header section of the message that was initially written by sender of email-message in his email client. But if we want to look at the full header section of the message (with those header lines containing envelope information, that were appended by SMTP servers) we can use options like "show original" in Gmail.
Since nobody answered, I will do it - I have spent some days looking for information about it, have read in RFC 5321 and RFC 5322 the most important moments and hasn't found any contradictions to the assumptions in the question.

Go (lang) parsing an email header and keeping order

I'm using net/mail library in Go, everything is great, however I want to pass in an original email and keep the order of the headers. This is important because the mail servers that pass the message on each add their headers in an order. Without order, its hard to know who received what, when and what headers each server added.
The net/mail library stores the headers in a map, which by definition has no concept of order. Seems a strange choice as header order is based only on order in the email, but it is the case.
Anyone got any suggestions as to how I can retain order the headers were read?
Thanks
The net/mail package uses the net/textproto package to parse the headers
(see ReadMessage()). Specifically, it uses ReadMIMEHeader() for
the headers, which is documented as:
The returned map m maps CanonicalMIMEHeaderKey(key) to a sequence of values
in the same order encountered in the input.
You can view the full source if you want, but the basic process is:
headers = make(map[string][]string)
for {
key, value := readNextHeader()
if key == "" {
return headers // End of headers
}
if headers[key] == nil {
headers[key] = []string{value}
} else {
headers[key] = append(headers[key], value)
}
}
It's true that the original order of the headers as they appeared in the message
is lost, but I'm not aware of any scenario where this truly matters. What
isn't lost is the order of the multi-values headers. The slice ensures they're
in the same order as they appeared in the email.
You can verify this with a simple program which loops over the headers and
compares the values (such as this one in the
Playground).
However, matching Received and Received-SPF headers is a bit more complex,
as:
not every Received header may have a corresponding Received-SPF header;
the Received-SPF header may not appear above the Received header; this is
recommended but not mandated by the RFC (besides, many programs don't
even follow the RFC, so this wouldn't be a guarantee anyway).
So you'll either need to parse the value of the headers and match them based on
that, or use the net/textproto package for more low-level access to the
headers. You can use the source of ReadMIMEHeader() as a starting point.

When would you use an unprotected JWS header?

I don't understand why JWS unprotected headers exist.
For some context: a JWS unprotected header contains parameters that are not integrity protected and can only be used per-signature with JSON Serialization.
If they could be used as a top-level header, I could see why someone could want to include a mutable parameter (that wouldn't change the signature). However, this is not the case.
Can anyone think of a use-case or know why they are included in the spec?
Thanks!
JWS Spec
The answer by Florent leaves me unsatisfied.
Regarding the example of using a JWT to sign a hash of a document... the assertion is that the algorithm and keyID would be "sensitive data" that needs to be "protected". By which I suppose he means "signed". But there's no need to sign the algorithm and keyID.
Example
Suppose Bob creates a signed JWT, that contains an unprotected header asserting alg=HS256 and keyid=XXXX1 . This JWT is intended for transmission to Alice.
Case 1
Suppose Mallory intercepts the signed JWT sent by Bob. Mallory then creates a new unprotected header, asserting alg=None.
The receiver (Alice) is now responsible for verifying the signature on the payload. Alice must not be satisfied with "no signature"; in fact Alice must not rely on a client (sender) assertion to determine which signing algorithm is acceptable for her. Therefore Alice rejects the JWT with the contrived "no signature" header.
Case 2
Suppose Mallory contrives a header with alg=RS256 and keyId=XXX1. Now Alice tries to validate the signature and finds either:
the algorithm is not compliant
the key specified for that algorithm does not exist
Therefore Alice rejects the JWT.
Case 3
Suppose Mallory contrives a header with alg=HS256 and keyId=ZZ3. Now Alice tries to validate the signature and finds the key is unknown, and rejects the JWT.
In no case does the algorithm need to be part of the signed material. There is no scenario under which an unprotected header leads to a vulnerability or violation of integrity.
Getting Back to the Original Question
The original question was: What is the purpose of an unprotected JWT header?
Succinctly, the purpose of an unprotected JWS header is to allow transport of some metadata that can be used as hints to the receiver. Like alg (Algorithm) and kid (Key ID). Florent suggests that stuffing data into an unprotected header could lead to efficiency. This isn't a good reason. Here is the key point: The claims in the unprotected header are hints, not to be relied upon or trusted.
A more interesting question is: What is the purpose of a protected JWS header? Why have a provision that signs both the "header" and the "payload"? In the case of a JWS Protected Header, the header and payload are concatenated and the result is signed. Assuming the header is JSON and the payload is JSON, at this point there is no semantic distinction between the header and payload. So why have the provision to sign the header at all?
One could just rely on JWS with unprotected headers. If there is a need for integrity-protected claims, put them in the payload. If there is a need for hints, put them in the unprotected header. Sign the payload and not the header. Simple.
This works, and is valid. But it presumes that the payload is JSON. This is true with JWT, but not true with all JWS. RFC 7515, which defines JWS, does not require the signed payload to be JSON. Imagine the payload is a digital image of a medical scan. It's not JSON. One cannot simply "attach claims" to that. Therefore JWS allows a protected header, such that the (non JSON) payload AND arbitrary claims can be signed and integrity checked.
In the case where the payload is non-JSON and the header is protected, there is no facility to include "extra non signed headers" into the JWS. If there is a need for sending some data that needs to be integrity checked and some that are simply "hints", there really is only one container: the protected header. And the hints get signed along with the real claims.
One could avoid the need for this protected-header trick, by just wrapping a JSON hash around the data-to-be-signed. For example:
{
"image" : "qw93u9839839...base64-encoded image data..."
}
And after doing so, one could add claims to this JSON wrapper.
{
"image" : "qw93u9839839...base64-encoded image data..."
"author" : "Whatever"
}
And those claims would then be signed and integrity-proected.
But in the case of binary data, encoding it to a string to allow encapsulation into a JSON may inflate the data significantly. A JWS with a non-JSON payload avoids this.
HTH
The RFC gives us examples of unprotected headers as follows:
A.6.2. JWS Per-Signature Unprotected Headers
Key ID values are supplied for both keys using per-signature Header Parameters. The two JWS Unprotected Header values used to represent these key IDs are:
{"kid":"2010-12-29"}
and
{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"}
https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.6.2
The use of kid in the example is likely not coincidence. Because JWS allows multiple signatures per payload, a cleartext hint system could be useful. For example, if a key is not available to the verifier (server), then you can skip decoding the protected header. The term "hint" is actually used in the kid definition:
4.1.4. "kid" (Key ID) Header Parameter
The "kid" (key ID) Header Parameter is a hint indicating which key was used to secure the JWS. This parameter allows originators to explicitly signal a change of key to recipients. The structure of the "kid" value is unspecified. Its value MUST be a case-sensitive string. Use of this Header Parameter is OPTIONAL.
https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.4
If we look at Key Identification it mentions where you a kid does not have to be integrity protected (ie: part of unprotected headers): (emphasis mine)
6. Key Identification
It is necessary for the recipient of a JWS to be able to determine the key that was employed for the digital signature or MAC operation. The key employed can be identified using the Header Parameter methods described in Section 4.1 or can be identified using methods that are outside the scope of this specification. Specifically, the Header Parameters "jku", "jwk", "kid", "x5u", "x5c", "x5t", and "x5t#S256" can be used to identify the key used. These Header Parameters MUST be integrity protected if the information that they convey is to be utilized in a trust decision; however, if the only information used in the trust decision is a key, these parameters need not be integrity protected, since changing them in a way that causes a different key to be used will cause the validation to fail.
The producer SHOULD include sufficient information in the Header Parameters to identify the key used, unless the application uses another means or convention to determine the key used. Validation of the signature or MAC fails when the algorithm used requires a key (which is true of all algorithms except for "none") and the key used cannot be determined.
The means of exchanging any shared symmetric keys used is outside the scope of this specification.
https://datatracker.ietf.org/doc/html/rfc7515#section-6
Simplified, if you have a message that by somebody modifying the kid will refer to another key, then the signature itself will not match. Therefore you don't have to include the kid in the protected header. A good example of the first part, where the information they convey is to be utilized in a trust decision, is the ACME (aka the Let's Encrypt protocol). When creating an account, and storing the key data, you want to trust the kid. We want to store kid, so we need to make sure it's valid. After the server has stored the kid and can use it to get a key, we can push messages and reference the kid in unprotected header (not done by ACME but possible). Since we're only going to verify the signature, then the kid is used a hint or reference to which kid was used for the account. If that field is tampered with, then it'll point to a nonexistent of completely different key and fail the signature the check. That means the kid itself is "the only information used in the trust decision".
There's also more theoretical scenarios that, knowing how it works you can come up with.
For example: the idea of having multiple signatures that you can pass on (exchange). A signing authority can include one signature that can be for an intermediary (server) and another for the another recipient (end-user client). This is differentiated by the kid and the server doesn't need to verify or even decode the protected header or signature. Or perhaps, the intermediary doesn't have the client's secret in order to verify a signature.
For example, a multi-recipient message (eg: chat room) could be processed by a relay/proxy and using kid in the unprotected header, pass along a reconstructed compact JWS (${protected}.${payload}.${signature}) for each recipient based on kid (or any other custom unprotected header field, like userId or endpoint).
Another example, would be a server with access to many different keys and a cleartext kid would be faster than iterating and decoded each protected field to find which one.
From one perspective, all you're doing is skipping base64url decoding the protected header for performance, but if you're going to proxy/relay the data, then you're not polluting the protected header which is meant for another recipient.

DKIM header tags ordering

Hi I am new to the mail domain and trying to figure out to parse the digital signature out of the DKIM-Signature header. The "b=" tag gives the value for the digital signature. Is there any ordering in where the "b=" tag should appear (is it always the last tag in the header)?
No there is no ordering in where "b" tag should appear in the DKIM-Signature header. You may very well know that DKIM is self-signatory i.e the signature is computed including the "DKIM signature" header. This has influence on why "b" tag is added at the end of the signature. It is just done for the sake of convenience of adding the tag's value at the end at once after the computation digital signature of DKIM's signature header.