goodkey: default to 110 rounds of Fermat factorization (#7579)

This change guarantees compliance with CA/BF Ballot SC-073 "Compromised
and Weak Keys", which requires that at least 100 rounds of Fermat
Factorization be attempted:

> Section 6.1.1.3 Subscriber Key Pair Generation
> The CA SHALL reject a certificate request if... The Public Key
corresponds to an industry-demonstrated weak Private Key. For requests
submitted on or after November 15, 2024,... In the case of Close Primes
vulnerability (https://fermatattack.secvuln.info/), the CA SHALL reject
weak keys which can be factored within 100 rounds using Fermat’s
factorization method.

We choose 110 rounds to ensure a margin above and beyond the requirements.

Fixes https://github.com/letsencrypt/boulder/issues/7558
This commit is contained in:
Aaron Gable 2024-07-17 16:05:30 -07:00 committed by GitHub
parent 2c157251ac
commit a3e99432bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 18 additions and 20 deletions

View File

@ -34,7 +34,7 @@ var kp goodkey.KeyPolicy
func init() {
var err error
kp, err = goodkey.NewPolicy(&goodkey.Config{FermatRounds: 100}, nil)
kp, err = goodkey.NewPolicy(nil, nil)
if err != nil {
log.Fatal("Could not create goodkey.KeyPolicy")
}

View File

@ -59,7 +59,7 @@ func init() {
if err != nil {
log.Fatal(err)
}
kp, err = sagoodkey.NewPolicy(&goodkey.Config{FermatRounds: 100}, nil)
kp, err = sagoodkey.NewPolicy(nil, nil)
if err != nil {
log.Fatal(err)
}

View File

@ -53,7 +53,7 @@ type Config struct {
// FermatRounds is an integer number of rounds of Fermat's factorization
// method that should be performed to attempt to detect keys whose modulus can
// be trivially factored because the two factors are very close to each other.
// If this config value is empty (0), no factorization will be attempted.
// If this config value is empty or 0, it will default to 110 rounds.
FermatRounds int
}
@ -122,7 +122,7 @@ type KeyPolicy struct {
// defaults. If the config's AllowedKeys is nil, the LetsEncryptCPS AllowedKeys
// is used. If the config's WeakKeyFile or BlockedKeyFile paths are empty, those
// checks are disabled. If the config's FermatRounds is 0, Fermat Factorization
// is disabled.
// defaults to attempting 110 rounds.
func NewPolicy(config *Config, bkc BlockedKeyCheckFunc) (KeyPolicy, error) {
if config == nil {
config = &Config{}
@ -149,10 +149,14 @@ func NewPolicy(config *Config, bkc BlockedKeyCheckFunc) (KeyPolicy, error) {
}
kp.blockedList = blocked
}
if config.FermatRounds < 0 {
return KeyPolicy{}, fmt.Errorf("Fermat factorization rounds cannot be negative: %d", config.FermatRounds)
}
if config.FermatRounds == 0 {
// The BRs require 100 rounds, so give ourselves a margin above that.
kp.fermatRounds = 110
} else if config.FermatRounds < 100 {
return KeyPolicy{}, fmt.Errorf("Fermat factorization rounds must be at least 100: %d", config.FermatRounds)
} else {
kp.fermatRounds = config.FermatRounds
}
return kp, nil
}
@ -354,13 +358,12 @@ func (policy *KeyPolicy) goodKeyRSA(key *rsa.PublicKey) error {
if rocacheck.IsWeak(key) {
return badKey("key generated by vulnerable Infineon-based hardware")
}
// Check if the key can be easily factored via Fermat's factorization method.
if policy.fermatRounds > 0 {
err := checkPrimeFactorsTooClose(modulus, policy.fermatRounds)
err = checkPrimeFactorsTooClose(modulus, policy.fermatRounds)
if err != nil {
return badKey("key generated with factors too close together: %w", err)
}
}
return nil
}

View File

@ -300,7 +300,7 @@ func TestDefaultAllowedKeys(t *testing.T) {
test.Assert(t, policy.allowedKeys.ECDSAP384, "NIST P384 should be allowed")
test.Assert(t, !policy.allowedKeys.ECDSAP521, "NIST P521 should not be allowed")
policy, err = NewPolicy(&Config{FermatRounds: 100}, nil)
policy, err = NewPolicy(&Config{}, nil)
test.AssertNotError(t, err, "NewPolicy with nil config.AllowedKeys failed")
test.Assert(t, policy.allowedKeys.RSA2048, "RSA 2048 should be allowed")
test.Assert(t, policy.allowedKeys.RSA3072, "RSA 3072 should be allowed")

View File

@ -143,8 +143,7 @@
"lifespanOCSP": "96h",
"goodkey": {
"weakKeyFile": "test/example-weak-keys.json",
"blockedKeyFile": "test/example-blocked-keys.yaml",
"fermatRounds": 100
"blockedKeyFile": "test/example-blocked-keys.yaml"
},
"ocspLogMaxLength": 4000,
"ocspLogPeriod": "500ms",

View File

@ -5,9 +5,6 @@
"maxOpenConns": 10
},
"hostnamePolicyFile": "test/hostname-policy.yaml",
"goodkey": {
"fermatRounds": 100
},
"workers": 16,
"unexpiredOnly": true,
"badResultsOnly": true,

View File

@ -8,8 +8,7 @@
"pendingAuthorizationLifetimeDays": 7,
"goodkey": {
"weakKeyFile": "test/example-weak-keys.json",
"blockedKeyFile": "test/example-blocked-keys.yaml",
"fermatRounds": 100
"blockedKeyFile": "test/example-blocked-keys.yaml"
},
"orderLifetime": "168h",
"finalizeTimeout": "30s",