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:
parent
2c157251ac
commit
a3e99432bb
|
@ -34,7 +34,7 @@ var kp goodkey.KeyPolicy
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var err error
|
var err error
|
||||||
kp, err = goodkey.NewPolicy(&goodkey.Config{FermatRounds: 100}, nil)
|
kp, err = goodkey.NewPolicy(nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not create goodkey.KeyPolicy")
|
log.Fatal("Could not create goodkey.KeyPolicy")
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ func init() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
kp, err = sagoodkey.NewPolicy(&goodkey.Config{FermatRounds: 100}, nil)
|
kp, err = sagoodkey.NewPolicy(nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ type Config struct {
|
||||||
// FermatRounds is an integer number of rounds of Fermat's factorization
|
// FermatRounds is an integer number of rounds of Fermat's factorization
|
||||||
// method that should be performed to attempt to detect keys whose modulus can
|
// 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.
|
// 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
|
FermatRounds int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ type KeyPolicy struct {
|
||||||
// defaults. If the config's AllowedKeys is nil, the LetsEncryptCPS AllowedKeys
|
// defaults. If the config's AllowedKeys is nil, the LetsEncryptCPS AllowedKeys
|
||||||
// is used. If the config's WeakKeyFile or BlockedKeyFile paths are empty, those
|
// 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
|
// 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) {
|
func NewPolicy(config *Config, bkc BlockedKeyCheckFunc) (KeyPolicy, error) {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = &Config{}
|
config = &Config{}
|
||||||
|
@ -149,10 +149,14 @@ func NewPolicy(config *Config, bkc BlockedKeyCheckFunc) (KeyPolicy, error) {
|
||||||
}
|
}
|
||||||
kp.blockedList = blocked
|
kp.blockedList = blocked
|
||||||
}
|
}
|
||||||
if config.FermatRounds < 0 {
|
if config.FermatRounds == 0 {
|
||||||
return KeyPolicy{}, fmt.Errorf("Fermat factorization rounds cannot be negative: %d", config.FermatRounds)
|
// 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
|
||||||
}
|
}
|
||||||
kp.fermatRounds = config.FermatRounds
|
|
||||||
return kp, nil
|
return kp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,12 +358,11 @@ func (policy *KeyPolicy) goodKeyRSA(key *rsa.PublicKey) error {
|
||||||
if rocacheck.IsWeak(key) {
|
if rocacheck.IsWeak(key) {
|
||||||
return badKey("key generated by vulnerable Infineon-based hardware")
|
return badKey("key generated by vulnerable Infineon-based hardware")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the key can be easily factored via Fermat's factorization method.
|
// 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 {
|
||||||
if err != nil {
|
return badKey("key generated with factors too close together: %w", err)
|
||||||
return badKey("key generated with factors too close together: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -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.ECDSAP384, "NIST P384 should be allowed")
|
||||||
test.Assert(t, !policy.allowedKeys.ECDSAP521, "NIST P521 should not 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.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.RSA2048, "RSA 2048 should be allowed")
|
||||||
test.Assert(t, policy.allowedKeys.RSA3072, "RSA 3072 should be allowed")
|
test.Assert(t, policy.allowedKeys.RSA3072, "RSA 3072 should be allowed")
|
||||||
|
|
|
@ -143,8 +143,7 @@
|
||||||
"lifespanOCSP": "96h",
|
"lifespanOCSP": "96h",
|
||||||
"goodkey": {
|
"goodkey": {
|
||||||
"weakKeyFile": "test/example-weak-keys.json",
|
"weakKeyFile": "test/example-weak-keys.json",
|
||||||
"blockedKeyFile": "test/example-blocked-keys.yaml",
|
"blockedKeyFile": "test/example-blocked-keys.yaml"
|
||||||
"fermatRounds": 100
|
|
||||||
},
|
},
|
||||||
"ocspLogMaxLength": 4000,
|
"ocspLogMaxLength": 4000,
|
||||||
"ocspLogPeriod": "500ms",
|
"ocspLogPeriod": "500ms",
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
"maxOpenConns": 10
|
"maxOpenConns": 10
|
||||||
},
|
},
|
||||||
"hostnamePolicyFile": "test/hostname-policy.yaml",
|
"hostnamePolicyFile": "test/hostname-policy.yaml",
|
||||||
"goodkey": {
|
|
||||||
"fermatRounds": 100
|
|
||||||
},
|
|
||||||
"workers": 16,
|
"workers": 16,
|
||||||
"unexpiredOnly": true,
|
"unexpiredOnly": true,
|
||||||
"badResultsOnly": true,
|
"badResultsOnly": true,
|
||||||
|
|
|
@ -8,8 +8,7 @@
|
||||||
"pendingAuthorizationLifetimeDays": 7,
|
"pendingAuthorizationLifetimeDays": 7,
|
||||||
"goodkey": {
|
"goodkey": {
|
||||||
"weakKeyFile": "test/example-weak-keys.json",
|
"weakKeyFile": "test/example-weak-keys.json",
|
||||||
"blockedKeyFile": "test/example-blocked-keys.yaml",
|
"blockedKeyFile": "test/example-blocked-keys.yaml"
|
||||||
"fermatRounds": 100
|
|
||||||
},
|
},
|
||||||
"orderLifetime": "168h",
|
"orderLifetime": "168h",
|
||||||
"finalizeTimeout": "30s",
|
"finalizeTimeout": "30s",
|
||||||
|
|
Loading…
Reference in New Issue