| author | Alberto Bertogli
<albertito@blitiri.com.ar> 2026-10-08 23:42:45 UTC |
| committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2026-10-09 20:34:10 UTC |
| parent | 2612c8bfa17839025a3c53cd3c852b2800de7c82 |
| internal/dkim/header.go | +6 | -2 |
| internal/dkim/header_test.go | +1 | -1 |
| internal/dkim/verify.go | +5 | -5 |
| internal/dkim/verify_test.go | +20 | -15 |
diff --git a/internal/dkim/header.go b/internal/dkim/header.go index 5558178..680c693 100644 --- a/internal/dkim/header.go +++ b/internal/dkim/header.go @@ -50,7 +50,9 @@ type dkimSignature struct { i string // Body octet count of the canonicalized body. - l uint64 + // nil if not present. Note that 0 is a valid value, and means that none + // of the body is signed. + l *uint64 // Query methods used for DNS lookup. // Colon-separated list of methods. Only "dns/txt" is valid. @@ -213,11 +215,13 @@ func dkimSignatureFromHeader(header string) (*dkimSignature, error) { sig.i = tags["i"] if tags["l"] != "" { - sig.l, err = strconv.ParseUint(tags["l"], 10, 64) + var l uint64 + l, err = strconv.ParseUint(tags["l"], 10, 64) if err != nil { return nil, fmt.Errorf("%w: failed to parse l: %w", errInvalidSignature, err) } + sig.l = &l } // q is a colon-separated list of query methods. diff --git a/internal/dkim/header_test.go b/internal/dkim/header_test.go index 1ebfbdf..dead777 100644 --- a/internal/dkim/header_test.go +++ b/internal/dkim/header_test.go @@ -40,7 +40,7 @@ func TestSignatureFromHeader(t *testing.T) { d: "example.com", h: []string{"from", "to", "subject"}, i: "agent@example.com", - l: 77, + l: new(uint64(77)), q: []string{"dns/txt"}, s: "selector", t: time.Unix(1600700888, 0), diff --git a/internal/dkim/verify.go b/internal/dkim/verify.go index 207b018..00b5107 100644 --- a/internal/dkim/verify.go +++ b/internal/dkim/verify.go @@ -189,19 +189,19 @@ func verifySignature(ctx context.Context, sigH header, // (if present). // https://datatracker.ietf.org/doc/html/rfc6376#section-3.7 bodyC := sig.cB.body(body) - if sig.l > 0 { + if sig.l != nil { // The l= value MUST NOT be larger than the canonicalized body. // https://datatracker.ietf.org/doc/html/rfc6376#section-3.5 - if sig.l > uint64(len(bodyC)) { + if *sig.l > uint64(len(bodyC)) { trace(ctx, "l= (%d) is larger than the body (%d)", - sig.l, len(bodyC)) + *sig.l, len(bodyC)) result.Error = fmt.Errorf( "%w: l= (%d) is larger than the body (%d)", - errInvalidTag, sig.l, len(bodyC)) + errInvalidTag, *sig.l, len(bodyC)) result.State = PERMFAIL return result } - bodyC = bodyC[:sig.l] + bodyC = bodyC[:*sig.l] } // Step 2: Compute the hash of the canonicalized body. diff --git a/internal/dkim/verify_test.go b/internal/dkim/verify_test.go index ead16f0..4bf718b 100644 --- a/internal/dkim/verify_test.go +++ b/internal/dkim/verify_test.go @@ -217,23 +217,26 @@ func TestVerifyBodyLength(t *testing.T) { "p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo="}, })) - // Use a valid body hash, so we can tell the l= cases apart: when l= is - // the body length, the body hash matches and we fail later on (because - // b= is bogus). + // Use a valid body hash (of the "signed" part of the body), so we can + // tell the l= cases apart: when l= covers exactly the signed part, the + // body hash matches and we fail later on (because b= is bogus). body := "Hi.\r\n" - bh := base64.StdEncoding.EncodeToString( - hashWith(crypto.SHA256, []byte(body))) cases := []struct { - l string - want error + l string + signed string + want error }{ - {"4", ErrBodyHashMismatch}, - {"5", ErrVerificationFailed}, - {"6", errInvalidTag}, - {"18446744073709551615", errInvalidTag}, + {"0", "", ErrVerificationFailed}, + {"0", body, ErrBodyHashMismatch}, + {"4", body, ErrBodyHashMismatch}, + {"5", body, ErrVerificationFailed}, + {"6", body, errInvalidTag}, + {"18446744073709551615", body, errInvalidTag}, } for _, c := range cases { + bh := base64.StdEncoding.EncodeToString( + hashWith(crypto.SHA256, []byte(c.signed))) message := toCRLF( "DKIM-Signature: v=1; a=ed25519-sha256; c=simple/simple;\n"+ " d=example.com; s=brisbane; h=from; l="+c.l+";\n"+ @@ -243,15 +246,17 @@ func TestVerifyBodyLength(t *testing.T) { res, err := VerifyMessage(ctx, message) if err != nil { - t.Fatalf("l=%s: VerifyMessage returned error: %v", c.l, err) + t.Fatalf("l=%s signed=%q: VerifyMessage returned error: %v", + c.l, c.signed, err) } if res.Found != 1 || res.Valid != 0 { - t.Fatalf("l=%s: wanted 1 found, 0 valid; got %v", c.l, res) + t.Fatalf("l=%s signed=%q: wanted 1 found, 0 valid; got %v", + c.l, c.signed, res) } r := res.Results[0] if r.State != PERMFAIL || !errors.Is(r.Error, c.want) { - t.Errorf("l=%s: wanted PERMFAIL / %v; got %v / %v", - c.l, c.want, r.State, r.Error) + t.Errorf("l=%s signed=%q: wanted PERMFAIL / %v; got %v / %v", + c.l, c.signed, c.want, r.State, r.Error) } } }