mirror of
https://blitiri.com.ar/repos/chasquid
synced 2026-10-29 15:02:30 +00:00
dkim: Limit size of RSA keys to 8192 bits
Operating on large RSA keys can be computationally expensive. Several implementations (e.g. crypto/tls, OpenSSL, rspamd) limit their size to prevent DoS via work amplification. This patch introduces that limit in our dkim library, set to 8192 bits. That's the same limit used by other implementations, like crypto/tls (which limits size of RSA keys in certificates) and rspamd (specifically for DKIM). It is also in line with the most recent DMARC.org report of key sizes in the wild (https://dmarc.org/stats/dkim/), where 8192 is already rare, and the largest one found.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user