Skip to content

Commit c5c4f3d

Browse files
FiloSottilegopherbot
authored andcommitted
crypto/x509: keep RSA CRT values in ParsePKCS1PrivateKey
Turns out that recomputing them (and qInv in particular) in constant time is expensive, so let's not throw them away when they are available. They are much faster to check, so we now do that on precompute. Also, thanks to the opaque crypto/internal/fips140/rsa.PrivateKey type, we now have some assurance that the values we use are always ones we checked. Recovers most of the performance loss since CL 630516 in the happy path. Also, since now we always use the CRT, if necessary by running a throwaway Precompute, which is now cheap if PrecomputedValues is filled out, we effectively fixed the JSON round-trip slowdown (#59695). goos: darwin goarch: arm64 pkg: crypto/rsa cpu: Apple M2 │ 3b42687 │ f017604bc6-dirty │ │ sec/op │ sec/op vs base │ ParsePKCS8PrivateKey/2048-8 26.76µ ± 1% 65.99µ ± 1% +146.64% (p=0.002 n=6) Fixes #59695 Updates #69799 For #69536 Change-Id: I507f8c5a32e69ab28990a3bf78959836b9b08cc9 Reviewed-on: https://go-review.googlesource.com/c/go/+/632478 Auto-Submit: Filippo Valsorda <[email protected]> Reviewed-by: Russ Cox <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Roland Shoemaker <[email protected]>
1 parent acd54c9 commit c5c4f3d

File tree

9 files changed

+206
-32
lines changed

9 files changed

+206
-32
lines changed

doc/godebug.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,11 @@ X25519MLKEM768 by default. The default can be reverted using the
208208
[`tlsmlkem` setting](/pkg/crypto/tls/#Config.CurvePreferences).
209209
Go 1.24 also removed X25519Kyber768Draft00 and the Go 1.23 `tlskyber` setting.
210210

211+
Go 1.24 made [`ParsePKCS1PrivateKey`](/pkg/crypto/x509/#ParsePKCS1PrivateKey)
212+
use and validate the CRT parameters in the encoded private key. This behavior
213+
can be controlled with the `x509rsacrt` setting. Using `x509rsacrt=0` restores
214+
the Go 1.23 behavior.
215+
211216
### Go 1.23
212217

213218
Go 1.23 changed the channels created by package time to be unbuffered
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
[MarshalPKCS8PrivateKey] now returns an error instead of marshaling an invalid
22
RSA key. ([MarshalPKCS1PrivateKey] doesn't have an error return, and its behavior
33
when provided invalid keys continues to be undefined.)
4+
5+
[ParsePKCS1PrivateKey] and [ParsePKCS8PrivateKey] now use and validate the
6+
encoded CRT values, so might reject invalid keys that were previously accepted.
7+
Use `GODEBUG=x509rsacrt=0` to revert to recomputing them.

src/crypto/internal/fips140/rsa/rsa.go

Lines changed: 91 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,43 @@ func newPrivateKey(n *bigmod.Modulus, e int, d *bigmod.Nat, p, q *bigmod.Modulus
104104
return pk, nil
105105
}
106106

107+
// NewPrivateKeyWithPrecomputation creates a new RSA private key from the given
108+
// parameters, which include precomputed CRT values.
109+
func NewPrivateKeyWithPrecomputation(N []byte, e int, d, P, Q, dP, dQ, qInv []byte) (*PrivateKey, error) {
110+
n, err := bigmod.NewModulus(N)
111+
if err != nil {
112+
return nil, err
113+
}
114+
p, err := bigmod.NewModulus(P)
115+
if err != nil {
116+
return nil, err
117+
}
118+
q, err := bigmod.NewModulus(Q)
119+
if err != nil {
120+
return nil, err
121+
}
122+
dN, err := bigmod.NewNat().SetBytes(d, n)
123+
if err != nil {
124+
return nil, err
125+
}
126+
qInvNat, err := bigmod.NewNat().SetBytes(qInv, p)
127+
if err != nil {
128+
return nil, err
129+
}
130+
131+
pk := &PrivateKey{
132+
pub: PublicKey{
133+
N: n, E: e,
134+
},
135+
d: dN, p: p, q: q,
136+
dP: dP, dQ: dQ, qInv: qInvNat,
137+
}
138+
if err := checkPrivateKey(pk); err != nil {
139+
return nil, err
140+
}
141+
return pk, nil
142+
}
143+
107144
// NewPrivateKeyWithoutCRT creates a new RSA private key from the given parameters.
108145
//
109146
// This is meant for deprecated multi-prime keys, and is not FIPS 140 compliant.
@@ -157,37 +194,64 @@ func checkPrivateKey(priv *PrivateKey) error {
157194
}
158195

159196
N := priv.pub.N
160-
Π := bigmod.NewNat().ExpandFor(N)
161-
for _, prime := range []*bigmod.Modulus{priv.p, priv.q} {
162-
p := prime.Nat().ExpandFor(N)
163-
if p.IsZero() == 1 || p.IsOne() == 1 {
164-
return errors.New("crypto/rsa: invalid prime")
165-
}
166-
Π.Mul(p, N)
197+
p := priv.p
198+
q := priv.q
167199

168-
// Check that de ≡ 1 mod p-1, for each prime.
169-
// This implies that e is coprime to each p-1 as e has a multiplicative
170-
// inverse. Therefore e is coprime to lcm(p-1,q-1,r-1,...) =
171-
// exponent(ℤ/nℤ). It also implies that a^de ≡ a mod p as a^(p-1) ≡ 1
172-
// mod p. Thus a^de ≡ a mod n for all a coprime to n, as required.
173-
174-
pMinus1, err := bigmod.NewModulus(p.SubOne(N).Bytes(N))
175-
if err != nil {
176-
return errors.New("crypto/rsa: invalid prime")
177-
}
200+
// Check that pq ≡ 1 mod N (and that pN < N and q < N).
201+
pN := bigmod.NewNat().ExpandFor(N)
202+
if _, err := pN.SetBytes(p.Nat().Bytes(p), N); err != nil {
203+
return errors.New("crypto/rsa: invalid prime")
204+
}
205+
qN := bigmod.NewNat().ExpandFor(N)
206+
if _, err := qN.SetBytes(q.Nat().Bytes(q), N); err != nil {
207+
return errors.New("crypto/rsa: invalid prime")
208+
}
209+
if pN.Mul(qN, N).IsZero() != 1 {
210+
return errors.New("crypto/rsa: p * q != n")
211+
}
178212

179-
e := bigmod.NewNat().SetUint(uint(priv.pub.E)).ExpandFor(pMinus1)
213+
// Check that de ≡ 1 mod p-1, and de ≡ 1 mod q-1.
214+
//
215+
// This implies that e is coprime to each p-1 as e has a multiplicative
216+
// inverse. Therefore e is coprime to lcm(p-1,q-1,r-1,...) = exponent(ℤ/nℤ).
217+
// It also implies that a^de ≡ a mod p as a^(p-1) ≡ 1 mod p. Thus a^de ≡ a
218+
// mod n for all a coprime to n, as required.
219+
//
220+
// This checks dP, dQ, and e. We don't check d because it is not actually
221+
// used in the RSA private key operation.
222+
pMinus1, err := bigmod.NewModulus(p.Nat().SubOne(p).Bytes(p))
223+
if err != nil {
224+
return errors.New("crypto/rsa: invalid prime")
225+
}
226+
dP, err := bigmod.NewNat().SetBytes(priv.dP, pMinus1)
227+
if err != nil {
228+
return errors.New("crypto/rsa: invalid CRT exponent")
229+
}
230+
de := bigmod.NewNat()
231+
de.SetUint(uint(priv.pub.E)).ExpandFor(pMinus1)
232+
de.Mul(dP, pMinus1)
233+
if de.IsOne() != 1 {
234+
return errors.New("crypto/rsa: invalid CRT exponent")
235+
}
180236

181-
de := bigmod.NewNat()
182-
de.Mod(priv.d, pMinus1)
183-
de.Mul(e, pMinus1)
184-
if de.IsOne() != 1 {
185-
return errors.New("crypto/rsa: invalid exponents")
186-
}
237+
qMinus1, err := bigmod.NewModulus(q.Nat().SubOne(q).Bytes(q))
238+
if err != nil {
239+
return errors.New("crypto/rsa: invalid prime")
240+
}
241+
dQ, err := bigmod.NewNat().SetBytes(priv.dQ, qMinus1)
242+
if err != nil {
243+
return errors.New("crypto/rsa: invalid CRT exponent")
244+
}
245+
de.SetUint(uint(priv.pub.E)).ExpandFor(qMinus1)
246+
de.Mul(dQ, qMinus1)
247+
if de.IsOne() != 1 {
248+
return errors.New("crypto/rsa: invalid CRT exponent")
187249
}
188-
// Check that Πprimes == n.
189-
if Π.IsZero() != 1 {
190-
return errors.New("crypto/rsa: invalid modulus")
250+
251+
// Check that qInv * q ≡ 1 mod p.
252+
one := q.Nat().Mul(priv.qInv, p)
253+
if one.IsOne() != 1 {
254+
return errors.New("crypto/rsa: invalid CRT coefficient")
191255
}
192256

193257
return nil

src/crypto/rsa/rsa.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,20 @@ func (priv *PrivateKey) precompute() (PrecomputedValues, error) {
519519
return precomputed, errors.New("crypto/rsa: prime Q is nil")
520520
}
521521

522+
// If the CRT values are already set, use them.
523+
if priv.Precomputed.Dp != nil && priv.Precomputed.Dq != nil && priv.Precomputed.Qinv != nil {
524+
k, err := rsa.NewPrivateKeyWithPrecomputation(priv.N.Bytes(), priv.E, priv.D.Bytes(),
525+
priv.Primes[0].Bytes(), priv.Primes[1].Bytes(),
526+
priv.Precomputed.Dp.Bytes(), priv.Precomputed.Dq.Bytes(), priv.Precomputed.Qinv.Bytes())
527+
if err != nil {
528+
return precomputed, err
529+
}
530+
precomputed = priv.Precomputed
531+
precomputed.fips = k
532+
precomputed.CRTValues = make([]CRTValue, 0)
533+
return precomputed, nil
534+
}
535+
522536
k, err := rsa.NewPrivateKey(priv.N.Bytes(), priv.E, priv.D.Bytes(),
523537
priv.Primes[0].Bytes(), priv.Primes[1].Bytes())
524538
if err != nil {

src/crypto/x509/pkcs1.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"crypto/rsa"
99
"encoding/asn1"
1010
"errors"
11+
"internal/godebug"
1112
"math/big"
1213
)
1314

@@ -19,10 +20,9 @@ type pkcs1PrivateKey struct {
1920
D *big.Int
2021
P *big.Int
2122
Q *big.Int
22-
// We ignore these values, if present, because rsa will calculate them.
23-
Dp *big.Int `asn1:"optional"`
24-
Dq *big.Int `asn1:"optional"`
25-
Qinv *big.Int `asn1:"optional"`
23+
Dp *big.Int `asn1:"optional"`
24+
Dq *big.Int `asn1:"optional"`
25+
Qinv *big.Int `asn1:"optional"`
2626

2727
AdditionalPrimes []pkcs1AdditionalRSAPrime `asn1:"optional,omitempty"`
2828
}
@@ -41,9 +41,16 @@ type pkcs1PublicKey struct {
4141
E int
4242
}
4343

44+
// x509rsacrt, if zero, makes ParsePKCS1PrivateKey ignore and recompute invalid
45+
// CRT values in the RSA private key.
46+
var x509rsacrt = godebug.New("x509rsacrt")
47+
4448
// ParsePKCS1PrivateKey parses an [RSA] private key in PKCS #1, ASN.1 DER form.
4549
//
4650
// This kind of key is commonly encoded in PEM blocks of type "RSA PRIVATE KEY".
51+
//
52+
// Before Go 1.24, the CRT parameters were ignored and recomputed. To restore
53+
// the old behavior, use the GODEBUG=x509rsacrt=0 environment variable.
4754
func ParsePKCS1PrivateKey(der []byte) (*rsa.PrivateKey, error) {
4855
var priv pkcs1PrivateKey
4956
rest, err := asn1.Unmarshal(der, &priv)
@@ -64,7 +71,8 @@ func ParsePKCS1PrivateKey(der []byte) (*rsa.PrivateKey, error) {
6471
return nil, errors.New("x509: unsupported private key version")
6572
}
6673

67-
if priv.N.Sign() <= 0 || priv.D.Sign() <= 0 || priv.P.Sign() <= 0 || priv.Q.Sign() <= 0 {
74+
if priv.N.Sign() <= 0 || priv.D.Sign() <= 0 || priv.P.Sign() <= 0 || priv.Q.Sign() <= 0 ||
75+
priv.Dp.Sign() <= 0 || priv.Dq.Sign() <= 0 || priv.Qinv.Sign() <= 0 {
6876
return nil, errors.New("x509: private key contains zero or negative value")
6977
}
7078

@@ -78,6 +86,9 @@ func ParsePKCS1PrivateKey(der []byte) (*rsa.PrivateKey, error) {
7886
key.Primes = make([]*big.Int, 2+len(priv.AdditionalPrimes))
7987
key.Primes[0] = priv.P
8088
key.Primes[1] = priv.Q
89+
key.Precomputed.Dp = priv.Dp
90+
key.Precomputed.Dq = priv.Dq
91+
key.Precomputed.Qinv = priv.Qinv
8192
for i, a := range priv.AdditionalPrimes {
8293
if a.Prime.Sign() <= 0 {
8394
return nil, errors.New("x509: private key contains zero or negative prime")
@@ -89,6 +100,19 @@ func ParsePKCS1PrivateKey(der []byte) (*rsa.PrivateKey, error) {
89100

90101
key.Precompute()
91102
if err := key.Validate(); err != nil {
103+
// If x509rsacrt=0 is set, try dropping the CRT values and
104+
// rerunning precomputation and key validation.
105+
if x509rsacrt.Value() == "0" {
106+
key.Precomputed.Dp = nil
107+
key.Precomputed.Dq = nil
108+
key.Precomputed.Qinv = nil
109+
key.Precompute()
110+
if err := key.Validate(); err == nil {
111+
x509rsacrt.IncNonDefault()
112+
return key, nil
113+
}
114+
}
115+
92116
return nil, err
93117
}
94118

src/crypto/x509/pkcs8.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ type pkcs8 struct {
3232
// in the future.
3333
//
3434
// This kind of key is commonly encoded in PEM blocks of type "PRIVATE KEY".
35+
//
36+
// Before Go 1.24, the CRT parameters of RSA keys were ignored and recomputed.
37+
// To restore the old behavior, use the GODEBUG=x509rsacrt=0 environment variable.
3538
func ParsePKCS8PrivateKey(der []byte) (key any, err error) {
3639
var privKey pkcs8
3740
if _, err := asn1.Unmarshal(der, &privKey); err != nil {

src/crypto/x509/x509_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,61 @@ func TestMarshalRSAPrivateKey(t *testing.T) {
253253
priv.Primes[2].Cmp(priv2.Primes[2]) != 0 {
254254
t.Errorf("wrong priv:\ngot %+v\nwant %+v", priv2, priv)
255255
}
256+
257+
if priv.Precomputed.Dp == nil {
258+
t.Fatalf("Precomputed.Dp is nil")
259+
}
260+
}
261+
262+
func TestMarshalRSAPrivateKeyInvalid(t *testing.T) {
263+
block, _ := pem.Decode([]byte(strings.ReplaceAll(
264+
`-----BEGIN RSA TESTING KEY-----
265+
MIIEowIBAAKCAQEAsPnoGUOnrpiSqt4XynxA+HRP7S+BSObI6qJ7fQAVSPtRkqso
266+
tWxQYLEYzNEx5ZSHTGypibVsJylvCfuToDTfMul8b/CZjP2Ob0LdpYrNH6l5hvFE
267+
89FU1nZQF15oVLOpUgA7wGiHuEVawrGfey92UE68mOyUVXGweJIVDdxqdMoPvNNU
268+
l86BU02vlBiESxOuox+dWmuVV7vfYZ79Toh/LUK43YvJh+rhv4nKuF7iHjVjBd9s
269+
B6iDjj70HFldzOQ9r8SRI+9NirupPTkF5AKNe6kUhKJ1luB7S27ZkvB3tSTT3P59
270+
3VVJvnzOjaA1z6Cz+4+eRvcysqhrRgFlwI9TEwIDAQABAoIBAEEYiyDP29vCzx/+
271+
dS3LqnI5BjUuJhXUnc6AWX/PCgVAO+8A+gZRgvct7PtZb0sM6P9ZcLrweomlGezI
272+
FrL0/6xQaa8bBr/ve/a8155OgcjFo6fZEw3Dz7ra5fbSiPmu4/b/kvrg+Br1l77J
273+
aun6uUAs1f5B9wW+vbR7tzbT/mxaUeDiBzKpe15GwcvbJtdIVMa2YErtRjc1/5B2
274+
BGVXyvlJv0SIlcIEMsHgnAFOp1ZgQ08aDzvilLq8XVMOahAhP1O2A3X8hKdXPyrx
275+
IVWE9bS9ptTo+eF6eNl+d7htpKGEZHUxinoQpWEBTv+iOoHsVunkEJ3vjLP3lyI/
276+
fY0NQ1ECgYEA3RBXAjgvIys2gfU3keImF8e/TprLge1I2vbWmV2j6rZCg5r/AS0u
277+
pii5CvJ5/T5vfJPNgPBy8B/yRDs+6PJO1GmnlhOkG9JAIPkv0RBZvR0PMBtbp6nT
278+
Y3yo1lwamBVBfY6rc0sLTzosZh2aGoLzrHNMQFMGaauORzBFpY5lU50CgYEAzPHl
279+
u5DI6Xgep1vr8QvCUuEesCOgJg8Yh1UqVoY/SmQh6MYAv1I9bLGwrb3WW/7kqIoD
280+
fj0aQV5buVZI2loMomtU9KY5SFIsPV+JuUpy7/+VE01ZQM5FdY8wiYCQiVZYju9X
281+
Wz5LxMNoz+gT7pwlLCsC4N+R8aoBk404aF1gum8CgYAJ7VTq7Zj4TFV7Soa/T1eE
282+
k9y8a+kdoYk3BASpCHJ29M5R2KEA7YV9wrBklHTz8VzSTFTbKHEQ5W5csAhoL5Fo
283+
qoHzFFi3Qx7MHESQb9qHyolHEMNx6QdsHUn7rlEnaTTyrXh3ifQtD6C0yTmFXUIS
284+
CW9wKApOrnyKJ9nI0HcuZQKBgQCMtoV6e9VGX4AEfpuHvAAnMYQFgeBiYTkBKltQ
285+
XwozhH63uMMomUmtSG87Sz1TmrXadjAhy8gsG6I0pWaN7QgBuFnzQ/HOkwTm+qKw
286+
AsrZt4zeXNwsH7QXHEJCFnCmqw9QzEoZTrNtHJHpNboBuVnYcoueZEJrP8OnUG3r
287+
UjmopwKBgAqB2KYYMUqAOvYcBnEfLDmyZv9BTVNHbR2lKkMYqv5LlvDaBxVfilE0
288+
2riO4p6BaAdvzXjKeRrGNEKoHNBpOSfYCOM16NjL8hIZB1CaV3WbT5oY+jp7Mzd5
289+
7d56RZOE+ERK2uz/7JX9VSsM/LbH9pJibd4e8mikDS9ntciqOH/3
290+
-----END RSA TESTING KEY-----`, "TESTING KEY", "PRIVATE KEY")))
291+
testRSA2048, _ := ParsePKCS1PrivateKey(block.Bytes)
292+
293+
broken := *testRSA2048
294+
broken.Precomputed.Dp = new(big.Int).SetUint64(42)
295+
296+
parsed, err := ParsePKCS1PrivateKey(MarshalPKCS1PrivateKey(&broken))
297+
if err == nil {
298+
t.Errorf("expected error, got success")
299+
}
300+
301+
t.Setenv("GODEBUG", "x509rsacrt=0")
302+
303+
parsed, err = ParsePKCS1PrivateKey(MarshalPKCS1PrivateKey(&broken))
304+
if err != nil {
305+
t.Fatalf("expected success, got error: %v", err)
306+
}
307+
// Dp should have been recomputed.
308+
if parsed.Precomputed.Dp.Cmp(testRSA2048.Precomputed.Dp) != 0 {
309+
t.Errorf("Dp recomputation failed: got %v, want %v", parsed.Precomputed.Dp, testRSA2048.Precomputed.Dp)
310+
}
256311
}
257312

258313
func TestMarshalRSAPublicKey(t *testing.T) {

src/internal/godebugs/table.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ var All = []Info{
6262
{Name: "winsymlink", Package: "os", Changed: 22, Old: "0"},
6363
{Name: "x509keypairleaf", Package: "crypto/tls", Changed: 23, Old: "0"},
6464
{Name: "x509negativeserial", Package: "crypto/x509", Changed: 23, Old: "1"},
65+
{Name: "x509rsacrt", Package: "crypto/x509", Changed: 24, Old: "0"},
6566
{Name: "x509usefallbackroots", Package: "crypto/x509"},
6667
{Name: "x509usepolicies", Package: "crypto/x509", Changed: 24, Old: "0"},
6768
{Name: "zipinsecurepath", Package: "archive/zip"},

src/runtime/metrics/doc.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,10 @@ Below is the full list of supported metrics, ordered lexicographically.
362362
package due to a non-default GODEBUG=x509negativeserial=...
363363
setting.
364364
365+
/godebug/non-default-behavior/x509rsacrt:events
366+
The number of non-default behaviors executed by the crypto/x509
367+
package due to a non-default GODEBUG=x509rsacrt=... setting.
368+
365369
/godebug/non-default-behavior/x509usefallbackroots:events
366370
The number of non-default behaviors executed by the crypto/x509
367371
package due to a non-default GODEBUG=x509usefallbackroots=...

0 commit comments

Comments
 (0)