From 10b54ffa51b134f7100e147742e5fc7d8d818a11 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Mon, 29 Jun 2026 19:13:41 +0200 Subject: [PATCH] ssh: raise the RSA modulus limit to 16384 bits the fix for CVE-2026-39829 capped the accepted RSA modulus at 8192 bits, matching crypto/tls, to bound the CPU cost of verifying an attacker-supplied key. That rejected keys OpenSSH generates: ssh-keygen allows -b up to 16384. Raise the limit to 16384 so those keys interoperate, both in the public key parser and the OpenSSH private key parser, and correspondingly raise the prime-factor bound to 8192 bits (each prime is about half the modulus). The agent's checkRSAKeyParams is updated to match. Verifying a 16384-bit key costs roughly 3x an 8192-bit one. Fixes golang/go#80075 Change-Id: Ib08ddd7b48552e692c48dbdf105bbc8579120bbc Reviewed-on: https://go-review.googlesource.com/c/crypto/+/795421 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com Reviewed-by: Junyang Shao Reviewed-by: Filippo Valsorda Reviewed-by: David Chase --- ssh/agent/server.go | 4 ++-- ssh/keys.go | 17 +++++++++-------- ssh/keys_test.go | 35 +++++++++++++++++++++-------------- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/ssh/agent/server.go b/ssh/agent/server.go index 782c54e..2739412 100644 --- a/ssh/agent/server.go +++ b/ssh/agent/server.go @@ -246,10 +246,10 @@ func setConstraints(key *AddedKey, constraintBytes []byte) error { // on arbitrary inputs; the CRT coefficient recomputation is cubic in // |p| and can consume excessive CPU on oversized keys. func checkRSAKeyParams(N, E, P, Q *big.Int) error { - if N.BitLen() > 8192 { + if N.BitLen() > 16384 { return errors.New("agent: RSA modulus too large") } - if P.BitLen() > 4096 || Q.BitLen() > 4096 { + if P.BitLen() > 8192 || Q.BitLen() > 8192 { return errors.New("agent: RSA prime too large") } if E.BitLen() > 24 { diff --git a/ssh/keys.go b/ssh/keys.go index 6437771..fbbfb6c 100644 --- a/ssh/keys.go +++ b/ssh/keys.go @@ -485,10 +485,11 @@ func parseRSA(in []byte) (out PublicKey, rest []byte, err error) { return nil, nil, err } - // 8192 bits is also the maximum RSA key size accepted by crypto/tls for - // signature verification: - // https://github.com/golang/go/blob/69801b25/src/crypto/tls/handshake_client.go#L1096 - if w.N.BitLen() > 8192 { + // 16384 bits is the largest RSA key OpenSSH will generate (ssh-keygen + // caps -b at 16384), so it is the practical upper bound for keys seen on + // the wire. Rejecting anything larger bounds the CPU spent verifying an + // attacker-supplied key and signature, mitigating a denial of service. + if w.N.BitLen() > 16384 { return nil, nil, errors.New("ssh: rsa modulus too large") } if w.E.BitLen() > 24 { @@ -1669,13 +1670,13 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv } // Mirror the validation done in parseRSA for public keys: cap the - // modulus at the same limit enforced by crypto/tls, reject oversized - // or invalid exponents, and additionally bound the prime factors to + // modulus at the OpenSSH-generated maximum, reject oversized or + // invalid exponents, and additionally bound the prime factors to // avoid the expensive CRT coefficient recomputation in pk.Precompute. - if key.N.BitLen() > 8192 { + if key.N.BitLen() > 16384 { return nil, errors.New("ssh: rsa modulus too large") } - if key.P.BitLen() > 4096 || key.Q.BitLen() > 4096 { + if key.P.BitLen() > 8192 || key.Q.BitLen() > 8192 { return nil, errors.New("ssh: rsa prime too large") } if key.E.BitLen() > 24 { diff --git a/ssh/keys_test.go b/ssh/keys_test.go index a16f7cd..2fb88e3 100644 --- a/ssh/keys_test.go +++ b/ssh/keys_test.go @@ -218,21 +218,32 @@ func TestParseRSAPrivateKey(t *testing.T) { } } -func TestParseRSAModulusTooLarge(t *testing.T) { +func TestParseRSAModulusLimit(t *testing.T) { + // A 16384-bit modulus is the largest OpenSSH will generate and must be + // accepted. rsa16384 := "ssh-rsa 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" - _, _, _, _, err := ParseAuthorizedKey([]byte(rsa16384)) - - if err == nil { - t.Fatal("ParseAuthorizedKey accepted a 16384-bit modulus; expected it to be rejected") + if _, _, _, _, err := ParseAuthorizedKey([]byte(rsa16384)); err != nil { + t.Fatalf("ParseAuthorizedKey rejected a 16384-bit modulus: %v", err) } + // A modulus larger than 16384 bits must be rejected to bound the cost of + // verifying an attacker-supplied key. + n := new(big.Int).Lsh(big.NewInt(1), 16384) // 16385 bits + pub, err := NewPublicKey(&rsa.PublicKey{N: n, E: 65537}) + if err != nil { + t.Fatalf("NewPublicKey: %v", err) + } + _, err = ParsePublicKey(pub.Marshal()) + if err == nil { + t.Fatal("ParsePublicKey accepted a modulus larger than 16384 bits; expected it to be rejected") + } expectedError := "rsa modulus too large" if !strings.Contains(err.Error(), expectedError) { t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError) } } -func TestParsePrivateKeyRSAModulusTooLarge(t *testing.T) { +func TestParsePrivateKeyRSAModulus16384(t *testing.T) { rsa16384 := []byte(`-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAIFwAAAAdzc2gtcn NhAAAAAwEAAQAACAEAy58lBbrFRB4r18MqVA625r4nzxjTkMlN5LxoG7ARC9L7px5lPs4o @@ -415,14 +426,10 @@ OthateBQGfnsGpTLxkpydh4jlYzTCp9bVRK8YXwsocglOcaRQH5ghfmgsAAAALbmljb2xh QHAxNnM= -----END OPENSSH PRIVATE KEY----- `) - _, err := ParseRawPrivateKey(rsa16384) - if err == nil { - t.Fatal("ParseRawPrivateKey accepted a 16384-bit modulus; expected it to be rejected") - } - - expectedError := "rsa modulus too large" - if !strings.Contains(err.Error(), expectedError) { - t.Errorf("unexpected error message: got %q, want substring %q", err.Error(), expectedError) + // A 16384-bit modulus is the largest OpenSSH will generate and must be + // accepted. + if _, err := ParseRawPrivateKey(rsa16384); err != nil { + t.Fatalf("ParseRawPrivateKey rejected a 16384-bit modulus: %v", err) } }