| author | Alberto Bertogli
<albertito@blitiri.com.ar> 2026-10-12 14:24:21 UTC |
| committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2026-10-12 14:36:02 UTC |
| parent | 71af456be75e6605e66015f098c10340add4867d |
| internal/dkim/dns.go | +17 | -0 |
| internal/dkim/dns_test.go | +36 | -0 |
diff --git a/internal/dkim/dns.go b/internal/dkim/dns.go index 8a2fdaa..ef03c86 100644 --- a/internal/dkim/dns.go +++ b/internal/dkim/dns.go @@ -167,6 +167,7 @@ var ( errInvalidRSAPublicKey = errors.New("invalid RSA public key") errNotRSAPublicKey = errors.New("not an RSA public key") errRSAKeyTooSmall = errors.New("RSA public key too small") + errRSAKeyTooLarge = errors.New("RSA public key too large") errInvalidEd25519Key = errors.New("invalid Ed25519 public key") ) @@ -192,6 +193,22 @@ func parseRSAPublicKey(p []byte) (verifyFunc, error) { return nil, errRSAKeyTooSmall } + // Enforce 8192-bit maximum. + // + // The RFC requires verifiers to handle keys of 1024 to 4096 bits, and + // does not set a maximum. But the key is attacker-controlled, and the + // cost of verifying grows quickly with its size: a key that fits in a + // TXT record can take seconds to verify. + // + // 8192 is chosen to be well above what legitimate signers use: + // - DMARC.org saw 277 keys of 8192 bits in 2025 (out of ~11.8M), and + // none larger. + // - rspamd and Go's crypto/tls (for certificates) use the same limit. + // https://datatracker.ietf.org/doc/html/rfc8301#section-3.2 + if rsaPub.Size()*8 > 8192 { + return nil, errRSAKeyTooLarge + } + return func(h crypto.Hash, hashed, signature []byte) error { return rsa.VerifyPKCS1v15(rsaPub, h, hashed, signature) }, nil diff --git a/internal/dkim/dns_test.go b/internal/dkim/dns_test.go index 6615224..4899604 100644 --- a/internal/dkim/dns_test.go +++ b/internal/dkim/dns_test.go @@ -3,8 +3,11 @@ package dkim import ( "context" "crypto" + "crypto/rsa" + "crypto/x509" "encoding/base64" "errors" + "math/big" "testing" "github.com/google/go-cmp/cmp" @@ -184,6 +187,39 @@ func TestParsePublicKey(t *testing.T) { } } +func TestRSAKeySize(t *testing.T) { + // Builds an RSA public key record with a modulus of the given size. The + // key doesn't need to be real, as long as it can be parsed. + keyRecord := func(bits int) string { + n := new(big.Int).Lsh(big.NewInt(1), uint(bits-1)) + n.SetBit(n, 0, 1) + der, err := x509.MarshalPKIXPublicKey(&rsa.PublicKey{N: n, E: 65537}) + if err != nil { + t.Fatalf("error marshalling %d-bit key: %v", bits, err) + } + return "p=" + base64.StdEncoding.EncodeToString(der) + } + + cases := []struct { + bits int + err error + }{ + {1024, nil}, + {2048, nil}, + {4096, nil}, + {8192, nil}, + {8193, errRSAKeyTooLarge}, + {16384, errRSAKeyTooLarge}, + {262144, errRSAKeyTooLarge}, + } + for _, c := range cases { + _, err := parsePublicKey(keyRecord(c.bits)) + if !errors.Is(err, c.err) { + t.Errorf("%d bits: want %v, got %v", c.bits, c.err, err) + } + } +} + func TestPublicKeyMatches(t *testing.T) { cases := []struct { pk *publicKey