mirror of
https://blitiri.com.ar/repos/chasquid
synced 2026-10-29 15:02:30 +00:00
b3b1f39d85
Today, we fold header names with strings.EqualFold when signing, but strings.ToLower when verifying. This should be harmless for valid messages (that have only ASCII characters in the header names), but if one contains an (invalid) header with a non-ASCII character, we could fail to verify the message we sign. The inconsistency would only happen if a header has a non-ASCII character that strings.EqualFold and strings.ToLower handle differently (like "ſubject", with U+017F). To make signing and verifying consistent, this patch makes signing use strings.ToLower. This bug was found by fuzzing the sign-verify cycle, which will be added in a later patch.
101 lines
2.5 KiB
Go
101 lines
2.5 KiB
Go
package dkim
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type header struct {
|
|
Name string
|
|
Value string
|
|
Source string
|
|
}
|
|
|
|
type headers []header
|
|
|
|
// FindAll the headers with the given name, in order of appearance.
|
|
func (h headers) FindAll(name string) headers {
|
|
hs := make(headers, 0)
|
|
for _, header := range h {
|
|
// Compare using strings.ToLower and not strings.EqualFold, to be
|
|
// consistent with the verifier (see headersToInclude). They differ
|
|
// for some non-ASCII characters, which are invalid in header names
|
|
// but can still appear in messages.
|
|
if strings.ToLower(header.Name) == strings.ToLower(name) {
|
|
hs = append(hs, header)
|
|
}
|
|
}
|
|
return hs
|
|
}
|
|
|
|
var errInvalidHeader = errors.New("invalid header")
|
|
|
|
// Parse a RFC822 message, return the headers, body, and error if any.
|
|
// We expect it to only contain CRLF line endings.
|
|
// Does NOT touch whitespace, this is important to preserve the original
|
|
// message and headers, which is required for the signature.
|
|
func parseMessage(message string) (headers, string, error) {
|
|
lines := strings.Split(message, "\r\n")
|
|
|
|
// Group the lines of each header: the first line, followed by its
|
|
// continuation lines (if any).
|
|
groups := [][]string{}
|
|
|
|
// End of headers. If there's no blank line, all the lines are headers.
|
|
eoh := len(lines)
|
|
for i, line := range lines {
|
|
if line == "" {
|
|
eoh = i
|
|
break
|
|
}
|
|
|
|
if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
|
|
// Continuation of the previous header.
|
|
if len(groups) == 0 {
|
|
return nil, "", fmt.Errorf(
|
|
"%w: bad continuation", errInvalidHeader)
|
|
}
|
|
last := len(groups) - 1
|
|
groups[last] = append(groups[last], line)
|
|
} else {
|
|
// New header.
|
|
groups = append(groups, []string{line})
|
|
}
|
|
}
|
|
|
|
headers := make(headers, 0, len(groups))
|
|
for _, g := range groups {
|
|
h, err := parseHeader(g)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
headers = append(headers, h)
|
|
}
|
|
|
|
// The body is everything after the blank line (if there is one).
|
|
body := ""
|
|
if eoh < len(lines) {
|
|
body = strings.Join(lines[eoh+1:], "\r\n")
|
|
}
|
|
|
|
return headers, body, nil
|
|
}
|
|
|
|
// Parse a header, given its lines (the first one, followed by the
|
|
// continuation lines, if any).
|
|
func parseHeader(lines []string) (header, error) {
|
|
// The colon must be in the first line.
|
|
if !strings.Contains(lines[0], ":") {
|
|
return header{}, fmt.Errorf("%w: no colon", errInvalidHeader)
|
|
}
|
|
|
|
source := strings.Join(lines, "\r\n")
|
|
name, value, _ := strings.Cut(source, ":")
|
|
return header{
|
|
Name: name,
|
|
Value: value,
|
|
Source: source,
|
|
}, nil
|
|
}
|