From a3e99432bb46ccecbf3bae13e24789322d4adec4 Mon Sep 17 00:00:00 2001 From: Aaron Gable Date: Wed, 17 Jul 2024 16:05:30 -0700 Subject: [PATCH] goodkey: default to 110 rounds of Fermat factorization (#7579) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- cmd/ceremony/main.go | 2 +- cmd/cert-checker/main_test.go | 2 +- goodkey/good_key.go | 23 +++++++++++++---------- goodkey/good_key_test.go | 2 +- test/config-next/ca.json | 3 +-- test/config-next/cert-checker.json | 3 --- test/config-next/ra.json | 3 +-- 7 files changed, 18 insertions(+), 20 deletions(-) diff --git a/cmd/ceremony/main.go b/cmd/ceremony/main.go index a026a461a..f18979fef 100644 --- a/cmd/ceremony/main.go +++ b/cmd/ceremony/main.go @@ -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") } diff --git a/cmd/cert-checker/main_test.go b/cmd/cert-checker/main_test.go index 3ebda1c80..eee0012b0 100644 --- a/cmd/cert-checker/main_test.go +++ b/cmd/cert-checker/main_test.go @@ -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) } diff --git a/goodkey/good_key.go b/goodkey/good_key.go index 04a075d35..4aa724c63 100644 --- a/goodkey/good_key.go +++ b/goodkey/good_key.go @@ -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 } - kp.fermatRounds = config.FermatRounds return kp, nil } @@ -354,12 +358,11 @@ 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) - if err != nil { - return badKey("key generated with factors too close together: %w", err) - } + err = checkPrimeFactorsTooClose(modulus, policy.fermatRounds) + if err != nil { + return badKey("key generated with factors too close together: %w", err) } return nil diff --git a/goodkey/good_key_test.go b/goodkey/good_key_test.go index e12e73c7a..04835b741 100644 --- a/goodkey/good_key_test.go +++ b/goodkey/good_key_test.go @@ -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") diff --git a/test/config-next/ca.json b/test/config-next/ca.json index cb02ee3dd..4124f4802 100644 --- a/test/config-next/ca.json +++ b/test/config-next/ca.json @@ -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", diff --git a/test/config-next/cert-checker.json b/test/config-next/cert-checker.json index a4e7d2179..4ed08af16 100644 --- a/test/config-next/cert-checker.json +++ b/test/config-next/cert-checker.json @@ -5,9 +5,6 @@ "maxOpenConns": 10 }, "hostnamePolicyFile": "test/hostname-policy.yaml", - "goodkey": { - "fermatRounds": 100 - }, "workers": 16, "unexpiredOnly": true, "badResultsOnly": true, diff --git a/test/config-next/ra.json b/test/config-next/ra.json index d727f93c1..ba51e906a 100644 --- a/test/config-next/ra.json +++ b/test/config-next/ra.json @@ -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",