diff --git a/crypto/internal/fips140/rsa/cast.go b/crypto/internal/fips140/rsa/cast.go index 1d5f78288c..be17ed580d 100644 --- a/crypto/internal/fips140/rsa/cast.go +++ b/crypto/internal/fips140/rsa/cast.go @@ -7,6 +7,7 @@ package rsa import ( "bytes" "errors" + "math/big" "sync" "github.com/runZeroInc/excrypto/crypto/internal/fips140" @@ -169,7 +170,7 @@ func testPrivateKey() *PrivateKey { 0xA7, 0x50, 0x6D, 0xEB, 0x52, 0x39, 0xA8, 0xA7} return &PrivateKey{ pub: PublicKey{ - N: N, E: 65537, + N: N, E: big.NewInt(65537), }, d: d, p: p, q: q, qInv: qInv, dP: dP, dQ: dQ, fipsApproved: true, diff --git a/crypto/internal/fips140/rsa/keygen.go b/crypto/internal/fips140/rsa/keygen.go index 4962a86b63..11aacbdb9d 100644 --- a/crypto/internal/fips140/rsa/keygen.go +++ b/crypto/internal/fips140/rsa/keygen.go @@ -7,6 +7,7 @@ package rsa import ( "errors" "io" + "math/big" "github.com/runZeroInc/excrypto/crypto/internal/fips140" "github.com/runZeroInc/excrypto/crypto/internal/fips140/bigmod" @@ -106,7 +107,7 @@ func GenerateKey(rand io.Reader, bits int) (*PrivateKey, error) { // negligible chance of failure we can defer the check to the end of key // generation and return an error if it fails. See [checkPrivateKey]. - k, err := newPrivateKey(N, 65537, d, P, Q) + k, err := newPrivateKey(N, big.NewInt(65537), d, P, Q) if err != nil { return nil, err } diff --git a/crypto/internal/fips140/rsa/rsa.go b/crypto/internal/fips140/rsa/rsa.go index f94d6e691c..81ca401531 100644 --- a/crypto/internal/fips140/rsa/rsa.go +++ b/crypto/internal/fips140/rsa/rsa.go @@ -7,6 +7,7 @@ package rsa import ( "bytes" "errors" + "math/big" "github.com/runZeroInc/excrypto/crypto/internal/fips140" "github.com/runZeroInc/excrypto/crypto/internal/fips140/bigmod" @@ -14,7 +15,7 @@ import ( type PublicKey struct { N *bigmod.Modulus - E int + E *big.Int } // Size returns the modulus size in bytes. Raw signatures and ciphertexts @@ -50,7 +51,10 @@ func (priv *PrivateKey) PublicKey() *PublicKey { // // All values are in big-endian byte slice format, and may have leading zeros // or be shorter if leading zeroes were trimmed. -func NewPrivateKey(N []byte, e int, d, P, Q []byte) (*PrivateKey, error) { +func NewPrivateKey(N []byte, e *big.Int, d, P, Q []byte) (*PrivateKey, error) { + if e == nil { + return nil, errors.New("crypto/rsa: missing public exponent") + } n, err := bigmod.NewModulus(N) if err != nil { return nil, err @@ -67,10 +71,11 @@ func NewPrivateKey(N []byte, e int, d, P, Q []byte) (*PrivateKey, error) { if err != nil { return nil, err } - return newPrivateKey(n, e, dN, p, q) + // Defensively copy e so callers cannot mutate the stored exponent. + return newPrivateKey(n, new(big.Int).Set(e), dN, p, q) } -func newPrivateKey(n *bigmod.Modulus, e int, d *bigmod.Nat, p, q *bigmod.Modulus) (*PrivateKey, error) { +func newPrivateKey(n *bigmod.Modulus, e *big.Int, d *bigmod.Nat, p, q *bigmod.Modulus) (*PrivateKey, error) { pMinusOne := p.Nat().SubOne(p) pMinusOneMod, err := bigmod.NewModulus(pMinusOne.Bytes(p)) if err != nil { @@ -95,8 +100,13 @@ func newPrivateKey(n *bigmod.Modulus, e int, d *bigmod.Nat, p, q *bigmod.Modulus qInv := bigmod.NewNat().Mod(q.Nat(), p) qInv.Exp(qInv, pMinusTwo, p) + if e == nil { + return nil, errors.New("crypto/rsa: missing public exponent") + } pk := &PrivateKey{ pub: PublicKey{ + // e is already a defensive copy from NewPrivateKey or a freshly + // generated value from keygen; store directly. N: n, E: e, }, d: d, p: p, q: q, @@ -110,7 +120,7 @@ func newPrivateKey(n *bigmod.Modulus, e int, d *bigmod.Nat, p, q *bigmod.Modulus // NewPrivateKeyWithPrecomputation creates a new RSA private key from the given // parameters, which include precomputed CRT values. -func NewPrivateKeyWithPrecomputation(N []byte, e int, d, P, Q, dP, dQ, qInv []byte) (*PrivateKey, error) { +func NewPrivateKeyWithPrecomputation(N []byte, e *big.Int, d, P, Q, dP, dQ, qInv []byte) (*PrivateKey, error) { n, err := bigmod.NewModulus(N) if err != nil { return nil, err @@ -132,9 +142,13 @@ func NewPrivateKeyWithPrecomputation(N []byte, e int, d, P, Q, dP, dQ, qInv []by return nil, err } + if e == nil { + return nil, errors.New("crypto/rsa: missing public exponent") + } pk := &PrivateKey{ pub: PublicKey{ - N: n, E: e, + // Defensively copy e so callers cannot mutate the stored exponent. + N: n, E: new(big.Int).Set(e), }, d: dN, p: p, q: q, dP: dP, dQ: dQ, qInv: qInvNat, @@ -148,7 +162,7 @@ func NewPrivateKeyWithPrecomputation(N []byte, e int, d, P, Q, dP, dQ, qInv []by // NewPrivateKeyWithoutCRT creates a new RSA private key from the given parameters. // // This is meant for deprecated multi-prime keys, and is not FIPS 140 compliant. -func NewPrivateKeyWithoutCRT(N []byte, e int, d []byte) (*PrivateKey, error) { +func NewPrivateKeyWithoutCRT(N []byte, e *big.Int, d []byte) (*PrivateKey, error) { n, err := bigmod.NewModulus(N) if err != nil { return nil, err @@ -157,9 +171,13 @@ func NewPrivateKeyWithoutCRT(N []byte, e int, d []byte) (*PrivateKey, error) { if err != nil { return nil, err } + if e == nil { + return nil, errors.New("crypto/rsa: missing public exponent") + } pk := &PrivateKey{ pub: PublicKey{ - N: n, E: e, + // Defensively copy e so callers cannot mutate the stored exponent. + N: n, E: new(big.Int).Set(e), }, d: dN, } @@ -173,9 +191,10 @@ func NewPrivateKeyWithoutCRT(N []byte, e int, d []byte) (*PrivateKey, error) { // // P, Q, dP, dQ, and qInv may be nil if the key was created with // NewPrivateKeyWithoutCRT. -func (priv *PrivateKey) Export() (N []byte, e int, d, P, Q, dP, dQ, qInv []byte) { +func (priv *PrivateKey) Export() (N []byte, e *big.Int, d, P, Q, dP, dQ, qInv []byte) { N = priv.pub.N.Nat().Bytes(priv.pub.N) - e = priv.pub.E + // Defensively copy E so the caller cannot mutate internal state. + e = new(big.Int).Set(priv.pub.E) d = priv.d.Bytes(priv.pub.N) if priv.dP == nil { return @@ -244,7 +263,13 @@ func checkPrivateKey(priv *PrivateKey) error { return errors.New("crypto/rsa: invalid CRT exponent") } de := bigmod.NewNat() - de.SetUint(uint(priv.pub.E)).ExpandFor(pMinus1) + // e may be larger than p-1 (the case for tiny test keys), so reduce + // modulo p-1 before loading into a bigmod.Nat sized for pMinus1. + ePmod := new(big.Int).Mod(priv.pub.E, new(big.Int).SetBytes(p.Nat().SubOne(p).Bytes(p))) + if _, err := de.SetBytes(ePmod.Bytes(), pMinus1); err != nil { + return errors.New("crypto/rsa: invalid public exponent") + } + de.ExpandFor(pMinus1) de.Mul(dP, pMinus1) if de.IsOne() != 1 { return errors.New("crypto/rsa: invalid CRT exponent") @@ -258,7 +283,11 @@ func checkPrivateKey(priv *PrivateKey) error { if err != nil { return errors.New("crypto/rsa: invalid CRT exponent") } - de.SetUint(uint(priv.pub.E)).ExpandFor(qMinus1) + eQmod := new(big.Int).Mod(priv.pub.E, new(big.Int).SetBytes(q.Nat().SubOne(q).Bytes(q))) + if _, err := de.SetBytes(eQmod.Bytes(), qMinus1); err != nil { + return errors.New("crypto/rsa: invalid public exponent") + } + de.ExpandFor(qMinus1) de.Mul(dQ, qMinus1) if de.IsOne() != 1 { return errors.New("crypto/rsa: invalid CRT exponent") @@ -341,25 +370,25 @@ func checkPublicKey(pub *PublicKey) (fipsApproved bool, err error) { if pub.N.BitLen()%2 == 1 { fipsApproved = false } - if pub.E < 2 { + if pub.E == nil || pub.E.Sign() <= 0 || pub.E.Cmp(big.NewInt(2)) < 0 { return false, errors.New("crypto/rsa: public exponent too small or negative") } // e needs to be coprime with p-1 and q-1, since it must be invertible // modulo λ(pq). Since p and q are prime, this means e needs to be odd. - if pub.E&1 == 0 { + if pub.E.Bit(0) == 0 { return false, errors.New("crypto/rsa: public exponent is even") } // FIPS 186-5, Section 5.5(e): "The exponent e shall be an odd, positive // integer such that 2¹⁶ < e < 2²⁵⁶." - if pub.E <= 1<<16 { + if pub.E.Cmp(big.NewInt(1<<16)) <= 0 { fipsApproved = false } - // We require pub.E to fit into a 32-bit integer so that we - // do not have different behavior depending on whether - // int is 32 or 64 bits. See also - // https://www.imperialviolet.org/2012/03/16/rsae.html. - if pub.E > 1<<31-1 { - return false, errors.New("crypto/rsa: public exponent too large") + // Previously FIPS required pub.E to fit in a 32-bit integer; excrypto + // relaxes this so that pathological public keys observed in the wild + // can still be parsed and inspected. Operations that depend on a small + // e (FIPS mode) are still gated above via fipsApproved. + if pub.E.BitLen() > 31 { + fipsApproved = false } return fipsApproved, nil } @@ -378,7 +407,7 @@ func encrypt(pub *PublicKey, plaintext []byte) ([]byte, error) { if err != nil { return nil, err } - return bigmod.NewNat().ExpShortVarTime(m, uint(pub.E), pub.N).Bytes(pub.N), nil + return bigmod.NewNat().Exp(m, pub.E.Bytes(), pub.N).Bytes(pub.N), nil } var ErrMessageTooLong = errors.New("crypto/rsa: message too long for RSA key size") @@ -440,7 +469,7 @@ func decrypt(priv *PrivateKey, ciphertext []byte, check bool) ([]byte, error) { } if check { - c1 := bigmod.NewNat().ExpShortVarTime(m, uint(E), N) + c1 := bigmod.NewNat().Exp(m, E.Bytes(), N) if c1.Equal(c) != 1 { return nil, ErrDecryption } diff --git a/crypto/internal/fips140test/acvp_test.go b/crypto/internal/fips140test/acvp_test.go index 396c4f8323..14184d38f5 100644 --- a/crypto/internal/fips140test/acvp_test.go +++ b/crypto/internal/fips140test/acvp_test.go @@ -1895,7 +1895,7 @@ func cmdRsaKeyGenAft() command { N, e, d, P, Q, _, _, _ := key.Export() eBytes := make([]byte, 4) - binary.BigEndian.PutUint32(eBytes, uint32(e)) + binary.BigEndian.PutUint32(eBytes, uint32(e.Uint64())) return [][]byte{eBytes, P, Q, N, d}, nil }, @@ -1933,7 +1933,7 @@ func cmdRsaSigGenAft(hashFunc func() hash.Hash, hashName string, pss bool) comma N, e, _, _, _, _, _, _ := key.Export() eBytes := make([]byte, 4) - binary.BigEndian.PutUint32(eBytes, uint32(e)) + binary.BigEndian.PutUint32(eBytes, uint32(e.Uint64())) return [][]byte{N, eBytes, sig}, nil }, @@ -1960,7 +1960,7 @@ func cmdRsaSigVerAft(hashFunc func() hash.Hash, hashName string, pss bool) comma pub := &rsa.PublicKey{ N: n, - E: e, + E: big.NewInt(int64(e)), } h := hashFunc() @@ -2050,7 +2050,7 @@ func cmdKtsIfcInitiatorAft(h func() hash.Hash) command { pub := &rsa.PublicKey{ N: n, - E: e, + E: big.NewInt(int64(e)), } dkm := make([]byte, outputBytes) @@ -2088,7 +2088,7 @@ func cmdKtsIfcResponderAft(h func() hash.Hash) command { return nil, errors.New("e must be 0x10001") } - priv, err := rsa.NewPrivateKey(nBytes, int(e), dBytes, pBytes, qBytes) + priv, err := rsa.NewPrivateKey(nBytes, big.NewInt(int64(e)), dBytes, pBytes, qBytes) if err != nil { return nil, fmt.Errorf("failed to create private key: %v", err) } diff --git a/crypto/json/rsa.go b/crypto/json/rsa.go index a44763c172..81f3759047 100644 --- a/crypto/json/rsa.go +++ b/crypto/json/rsa.go @@ -28,9 +28,9 @@ type RSAPublicKey struct { } type auxRSAPublicKey struct { - Exponent int `json:"exponent"` - Modulus []byte `json:"modulus"` - Length int `json:"length"` + Exponent json.Number `json:"exponent"` + Modulus []byte `json:"modulus"` + Length int `json:"length"` } // RSAClientParams are the TLS key exchange parameters for RSA keys. @@ -43,7 +43,9 @@ type RSAClientParams struct { func (rp *RSAPublicKey) MarshalJSON() ([]byte, error) { var aux auxRSAPublicKey if rp.PublicKey != nil { - aux.Exponent = rp.E + if rp.E != nil { + aux.Exponent = json.Number(rp.E.String()) + } aux.Modulus = rp.N.Bytes() aux.Length = len(aux.Modulus) * 8 } @@ -59,7 +61,12 @@ func (rp *RSAPublicKey) UnmarshalJSON(b []byte) error { if rp.PublicKey == nil { rp.PublicKey = new(rsa.PublicKey) } - rp.E = aux.Exponent + rp.E = new(big.Int) + if s := string(aux.Exponent); s != "" { + if _, ok := rp.E.SetString(s, 10); !ok { + return fmt.Errorf("invalid RSA public exponent %q", s) + } + } rp.N = big.NewInt(0).SetBytes(aux.Modulus) if len(aux.Modulus)*8 != aux.Length { return fmt.Errorf("mismatched length (got %d, field specified %d)", len(aux.Modulus), aux.Length) diff --git a/crypto/json/rsa_test.go b/crypto/json/rsa_test.go index d4fe5e77ff..5a778f3cb9 100644 --- a/crypto/json/rsa_test.go +++ b/crypto/json/rsa_test.go @@ -37,7 +37,7 @@ var _ = Suite(&RSASuite{}) func (s *RSASuite) SetUpTest(c *C) { s.pk4096 = new(RSAPublicKey) s.pk4096.PublicKey = new(rsa.PublicKey) - s.pk4096.E = 65537 + s.pk4096.E = big.NewInt(65537) s.pk4096.N = big.NewInt(0).SetBytes(test4096Modulus) } diff --git a/crypto/rsa/bench_e_test.go b/crypto/rsa/bench_e_test.go new file mode 100644 index 0000000000..9b915af553 --- /dev/null +++ b/crypto/rsa/bench_e_test.go @@ -0,0 +1,56 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rsa_test + +import ( + "crypto/rand" + "math/big" + "testing" + + . "github.com/runZeroInc/excrypto/crypto/rsa" +) + +// BenchmarkVerifyByExponentBitLen measures the cost of a single PKCS#1 v1.5 +// signature verification on a 2048-bit RSA modulus as E grows. The verifier +// performs m^e mod n; modular exponentiation is O(bitlen(e) · bitlen(n)²). +func BenchmarkVerifyByExponentBitLen(b *testing.B) { + priv, err := GenerateKey(rand.Reader, 2048) + if err != nil { + b.Fatal(err) + } + // Build a synthetic odd E of the requested bit length, coprime to phi(n) + // is not required here: we measure CPU of the exponentiation itself, and + // any odd E exercises the same code path. + makeE := func(bits int) *big.Int { + e := new(big.Int).Lsh(big.NewInt(1), uint(bits-1)) + e.SetBit(e, 0, 1) + return e + } + for _, bits := range []int{17, 64, 128, 256, 512, 1024, 2048} { + b.Run("E="+itoa(bits)+"b", func(b *testing.B) { + pub := &PublicKey{N: priv.N, E: makeE(bits)} + msg := []byte("x") + // Use Encrypt as a proxy for the public-key operation. + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = EncryptPKCS1v15(rand.Reader, pub, msg) + } + }) + } +} + +func itoa(n int) string { + if n == 0 { + return "0" + } + var b [20]byte + i := len(b) + for n > 0 { + i-- + b[i] = byte('0' + n%10) + n /= 10 + } + return string(b[i:]) +} diff --git a/crypto/rsa/boring_test.go b/crypto/rsa/boring_test.go index c14488633e..d544003f5c 100644 --- a/crypto/rsa/boring_test.go +++ b/crypto/rsa/boring_test.go @@ -39,7 +39,7 @@ func TestBoringVerify(t *testing.T) { // Check that signatures that lack leading zeroes don't verify. key := &PublicKey{ N: bigFromHex("c4fdf7b40a5477f206e6ee278eaef888ca73bf9128a9eef9f2f1ddb8b7b71a4c07cfa241f028a04edb405e4d916c61d6beabc333813dc7b484d2b3c52ee233c6a79b1eea4e9cc51596ba9cd5ac5aeb9df62d86ea051055b79d03f8a4fa9f38386f5bd17529138f3325d46801514ea9047977e0829ed728e68636802796801be1"), - E: 65537, + E: big.NewInt(65537), } hash := fromHex("019c5571724fb5d0e47a4260c940e9803ba05a44") @@ -63,7 +63,7 @@ func BenchmarkBoringVerify(b *testing.B) { // Check that signatures that lack leading zeroes don't verify. key := &PublicKey{ N: bigFromHex("c4fdf7b40a5477f206e6ee278eaef888ca73bf9128a9eef9f2f1ddb8b7b71a4c07cfa241f028a04edb405e4d916c61d6beabc333813dc7b484d2b3c52ee233c6a79b1eea4e9cc51596ba9cd5ac5aeb9df62d86ea051055b79d03f8a4fa9f38386f5bd17529138f3325d46801514ea9047977e0829ed728e68636802796801be1"), - E: 65537, + E: big.NewInt(65537), } hash := fromHex("019c5571724fb5d0e47a4260c940e9803ba05a44") diff --git a/crypto/rsa/fips.go b/crypto/rsa/fips.go index 9967b6ee67..93f0c031e3 100644 --- a/crypto/rsa/fips.go +++ b/crypto/rsa/fips.go @@ -8,6 +8,7 @@ import ( "errors" "hash" "io" + "math/big" "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/internal/boring" @@ -429,10 +430,10 @@ func checkFIPS140OnlyPublicKey(pub *PublicKey) error { if pub.N.BitLen()%2 == 1 { return errors.New("crypto/rsa: use of keys with odd size is not allowed in FIPS 140-only mode") } - if pub.E <= 1<<16 { + if pub.E == nil || pub.E.Cmp(big.NewInt(1<<16)) <= 0 { return errors.New("crypto/rsa: use of public exponent <= 2¹⁶ is not allowed in FIPS 140-only mode") } - if pub.E&1 == 0 { + if pub.E.Bit(0) == 0 { return errors.New("crypto/rsa: use of even public exponent is not allowed in FIPS 140-only mode") } return nil diff --git a/crypto/rsa/pss_test.go b/crypto/rsa/pss_test.go index 0d17edacb2..74eebae3f6 100644 --- a/crypto/rsa/pss_test.go +++ b/crypto/rsa/pss_test.go @@ -91,7 +91,7 @@ func TestPSSGolden(t *testing.T) { continue } key.N = bigFromHex(nHex) - key.E = intFromHex(<-values) + key.E = big.NewInt(int64(intFromHex(<-values))) // We don't care for d, p, q, dP, dQ or qInv. for i := 0; i < 6; i++ { <-values diff --git a/crypto/rsa/rsa.go b/crypto/rsa/rsa.go index 6fb91cf2db..c22a99a31a 100644 --- a/crypto/rsa/rsa.go +++ b/crypto/rsa/rsa.go @@ -67,7 +67,7 @@ var bigOne = big.NewInt(1) // side channels, or could be mathematically derived from other public values. type PublicKey struct { N *big.Int // modulus - E int // public exponent + E *big.Int // public exponent } // Any methods implemented on PublicKey might need to also be implemented on @@ -85,7 +85,7 @@ func (pub *PublicKey) Equal(x crypto.PublicKey) bool { if !ok { return false } - return bigIntEqual(pub.N, xx.N) && pub.E == xx.E + return bigIntEqual(pub.N, xx.N) && bigIntEqual(pub.E, xx.E) } // OAEPOptions allows passing options to OAEP encryption and decryption @@ -254,7 +254,7 @@ func (priv *PrivateKey) precomputedIsConsistent() bool { return false } N, e, d, P, Q, dP, dQ, qInv := priv.Precomputed.fips.Export() - if !bigIntEqualToBytes(priv.N, N) || priv.E != e || !bigIntEqualToBytes(priv.D, d) { + if !bigIntEqualToBytes(priv.N, N) || !bigIntEqual(priv.E, e) || !bigIntEqualToBytes(priv.D, d) { return false } if len(priv.Primes) != 2 { @@ -290,10 +290,54 @@ func checkKeySize(size int) error { return nil } +// MaxPublicExponentBitLen bounds the bit length of the RSA public exponent E +// accepted by public-key operations such as [EncryptPKCS1v15], [EncryptOAEP], +// [VerifyPKCS1v15], and [VerifyPSS]. Parsing functions in this package and in +// [crypto/x509] do not consult this variable — only operations that actually +// evaluate the exponent are gated — so it is always safe to inspect a key +// that exceeds the bound. +// +// The default of 1024 is a permissive ceiling chosen to accommodate the +// pathological-but-real RSA public keys this fork is intended to scan and +// inspect, while still capping worst-case modular-exponentiation cost. Every +// legitimate RSA public exponent observed in the wild (e = 3, 17, 65537, +// etc.) has a bit length well under 32, and FIPS 186-5 requires e < 2²⁵⁶. +// +// Modular exponentiation cost is O(bitlen(E) · bitlen(N)²). Indicative +// per-operation costs measured on Apple M3 Max with a 2048-bit modulus: +// +// bitlen(E) = 17 ≈ 56 µs (e = 65537, the common case) +// bitlen(E) = 256 ≈ 340 µs (FIPS 186-5 upper bound) +// bitlen(E) = 1024 ≈ 1.25 ms (this default) +// bitlen(E) = 2048 ≈ 2.5 ms (E as wide as N; absolute worst case) +// +// Tighten the bound for DoS-sensitive deployments that only need to handle +// well-formed keys: +// +// rsa.MaxPublicExponentBitLen = 256 // FIPS 186-5 ceiling +// rsa.MaxPublicExponentBitLen = 31 // stdlib's historical 32-bit limit +// rsa.MaxPublicExponentBitLen = 17 // effectively restrict to e ≤ 65537 +// +// Loosen or disable the bound entirely to validate signatures on arbitrarily +// pathological keys: +// +// rsa.MaxPublicExponentBitLen = 0 // accept any E +// +// The variable is process-global. Callers that need different bounds for +// different operations should snapshot, set, and restore it around the +// operation under a sync mechanism of their choice. +var MaxPublicExponentBitLen = 1024 + func checkPublicKeySize(k *PublicKey) error { if k.N == nil { return errors.New("crypto/rsa: missing public modulus") } + if k.E == nil { + return errors.New("crypto/rsa: missing public exponent") + } + if MaxPublicExponentBitLen > 0 && k.E.BitLen() > MaxPublicExponentBitLen { + return errors.New("crypto/rsa: public exponent exceeds MaxPublicExponentBitLen") + } return checkKeySize(k.N.BitLen()) } @@ -326,15 +370,10 @@ func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) { Dp := bbig.Dec(bDp) Dq := bbig.Dec(bDq) Qinv := bbig.Dec(bQinv) - e64 := E.Int64() - if !E.IsInt64() || int64(int(e64)) != e64 { - return nil, errors.New("crypto/rsa: generated key exponent too large") - } - key := &PrivateKey{ PublicKey: PublicKey{ N: N, - E: int(e64), + E: E, }, D: D, Primes: []*big.Int{P, Q}, @@ -435,7 +474,7 @@ func GenerateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*PrivateKey random = rand.CustomReader(random) priv := new(PrivateKey) - priv.E = 65537 + priv.E = big.NewInt(65537) if nprimes < 2 { return nil, errors.New("crypto/rsa: GenerateMultiPrimeKey: nprimes must be >= 2") @@ -509,7 +548,7 @@ NextSetOfPrimes: } priv.D = new(big.Int) - e := big.NewInt(int64(priv.E)) + e := priv.E ok := priv.D.ModInverse(e, totient) if ok != nil { @@ -665,7 +704,14 @@ func fipsPublicKey(pub *PublicKey) (*rsa.PublicKey, error) { if err != nil { return nil, err } - return &rsa.PublicKey{N: N, E: pub.E}, nil + // Defensively copy E so that mutations to the caller's PublicKey after + // this call cannot affect the internal FIPS state used by subsequent + // operations on this key. + if pub.E == nil { + return nil, errors.New("crypto/rsa: missing public exponent") + } + e := new(big.Int).Set(pub.E) + return &rsa.PublicKey{N: N, E: e}, nil } func fipsPrivateKey(priv *PrivateKey) (*rsa.PrivateKey, error) { diff --git a/crypto/rsa/rsa_test.go b/crypto/rsa/rsa_test.go index 5f4fa60030..f2659268c4 100644 --- a/crypto/rsa/rsa_test.go +++ b/crypto/rsa/rsa_test.go @@ -864,7 +864,7 @@ func TestEncryptOAEP(t *testing.T) { n := new(big.Int) for i, test := range testEncryptOAEPData { n.SetString(test.modulus, 16) - public := PublicKey{N: n, E: test.e} + public := PublicKey{N: n, E: big.NewInt(int64(test.e))} for j, message := range test.msgs { randomSource := bytes.NewReader(message.seed) @@ -889,7 +889,7 @@ func TestDecryptOAEP(t *testing.T) { n.SetString(test.modulus, 16) d.SetString(test.d, 16) private := new(PrivateKey) - private.PublicKey = PublicKey{N: n, E: test.e} + private.PublicKey = PublicKey{N: n, E: big.NewInt(int64(test.e))} private.D = d for j, message := range test.msgs { @@ -925,7 +925,7 @@ func Test2DecryptOAEP(t *testing.T) { n.SetString(testEncryptOAEPData[0].modulus, 16) d.SetString(testEncryptOAEPData[0].d, 16) priv := new(PrivateKey) - priv.PublicKey = PublicKey{N: n, E: testEncryptOAEPData[0].e} + priv.PublicKey = PublicKey{N: n, E: big.NewInt(int64(testEncryptOAEPData[0].e))} priv.D = d sha1 := crypto.SHA1 sha256 := crypto.SHA256 @@ -947,7 +947,7 @@ func TestEncryptDecryptOAEP(t *testing.T) { n.SetString(test.modulus, 16) d.SetString(test.d, 16) priv := new(PrivateKey) - priv.PublicKey = PublicKey{N: n, E: test.e} + priv.PublicKey = PublicKey{N: n, E: big.NewInt(int64(test.e))} priv.D = d for j, message := range test.msgs { @@ -1319,12 +1319,12 @@ func TestModifiedPrivateKey(t *testing.T) { t.Run("E+2", func(t *testing.T) { testModifiedPrivateKey(t, func(k *PrivateKey) { - k.E += 2 + k.E = new(big.Int).Add(k.E, big.NewInt(2)) }) }) t.Run("E=0", func(t *testing.T) { testModifiedPrivateKey(t, func(k *PrivateKey) { - k.E = 0 + k.E = big.NewInt(0) }) }) } @@ -1342,3 +1342,66 @@ func testModifiedPrivateKey(t *testing.T, f func(*PrivateKey)) { t.Error("Validate should have failed after Precompute()") } } + +// TestMaxPublicExponentBitLen verifies the DoS-mitigation knob: setting +// MaxPublicExponentBitLen rejects key operations with an oversized exponent, +// while parsing the key remains unaffected. +func TestMaxPublicExponentBitLen(t *testing.T) { + k, err := GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatal(err) + } + // Replace E with a large odd exponent (still coprime to phi for our purposes; + // we only test the gating, not a full encrypt round-trip). + bigE := new(big.Int).Lsh(big.NewInt(1), 100) + bigE.SetBit(bigE, 0, 1) // make odd + + pub := &PublicKey{N: k.N, E: bigE} + + prev := MaxPublicExponentBitLen + defer func() { MaxPublicExponentBitLen = prev }() + + MaxPublicExponentBitLen = 64 + _, err = EncryptPKCS1v15(rand.Reader, pub, []byte("x")) + if err == nil || !strings.Contains(err.Error(), "MaxPublicExponentBitLen") { + t.Errorf("expected MaxPublicExponentBitLen error, got %v", err) + } + + MaxPublicExponentBitLen = 0 // disabled => no upper bound + // With the bound disabled we still expect the call to be admitted into the + // modular-exponentiation path. (We do not assert success because bigE is + // not coprime with phi(N); only that the size gate does not reject it.) + _, err = EncryptPKCS1v15(rand.Reader, pub, []byte("x")) + if err != nil && strings.Contains(err.Error(), "MaxPublicExponentBitLen") { + t.Errorf("MaxPublicExponentBitLen=0 should not gate: %v", err) + } +} + +// TestPublicKeyDefensiveCopy verifies that mutating the caller's PublicKey +// after performing an operation does not affect subsequent operations on the +// same key via the precomputed FIPS state. +func TestPublicKeyDefensiveCopy(t *testing.T) { + priv, err := GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatal(err) + } + msg := []byte("hello") + ct, err := EncryptPKCS1v15(rand.Reader, &priv.PublicKey, msg) + if err != nil { + t.Fatal(err) + } + pt, err := DecryptPKCS1v15(rand.Reader, priv, ct) + if err != nil || !bytes.Equal(pt, msg) { + t.Fatalf("decrypt mismatch: %v / %x", err, pt) + } + // Mutate the caller's E in place. A correctly defensively-copied internal + // state must continue to function. + originalE := new(big.Int).Set(priv.PublicKey.E) + priv.PublicKey.E.SetInt64(7) + pt2, err := DecryptPKCS1v15(rand.Reader, priv, ct) + // Restore E so subsequent tests aren't affected. + priv.PublicKey.E.Set(originalE) + if err != nil || !bytes.Equal(pt2, msg) { + t.Fatalf("decrypt after caller-side E mutation failed (defensive copy missing?): %v / %x", err, pt2) + } +} diff --git a/crypto/ssl3/tls/handshake_server_test.go b/crypto/ssl3/tls/handshake_server_test.go index efd022264d..ed0be7c59f 100644 --- a/crypto/ssl3/tls/handshake_server_test.go +++ b/crypto/ssl3/tls/handshake_server_test.go @@ -955,7 +955,7 @@ var testSNICertificate = fromHex("308201f23082015da003020102020100300b06092a8648 var testRSAPrivateKey = &rsa.PrivateKey{ PublicKey: rsa.PublicKey{ N: bigFromString("131650079503776001033793877885499001334664249354723305978524647182322416328664556247316495448366990052837680518067798333412266673813370895702118944398081598789828837447552603077848001020611640547221687072142537202428102790818451901395596882588063427854225330436740647715202971973145151161964464812406232198521"), - E: 65537, + E: big.NewInt(65537), }, D: bigFromString("29354450337804273969007277378287027274721892607543397931919078829901848876371746653677097639302788129485893852488285045793268732234230875671682624082413996177431586734171663258657462237320300610850244186316880055243099640544518318093544057213190320837094958164973959123058337475052510833916491060913053867729"), Primes: []*big.Int{ diff --git a/crypto/ssl3/tls/key_agreement.go b/crypto/ssl3/tls/key_agreement.go index edb9345a5d..f02fbf7deb 100644 --- a/crypto/ssl3/tls/key_agreement.go +++ b/crypto/ssl3/tls/key_agreement.go @@ -62,7 +62,7 @@ func (ka *rsaKeyAgreement) generateServerKeyExchange(config *Config, cert *Certi // Serialize the key parameters to a nice byte array. The byte array can be // positioned later. modulus := ka.privateKey.N.Bytes() - exponent := big.NewInt(int64(ka.privateKey.E)).Bytes() + exponent := ka.privateKey.E.Bytes() serverRSAParams := make([]byte, 0, 2+len(modulus)+2+len(exponent)) serverRSAParams = append(serverRSAParams, byte(len(modulus)>>8), byte(len(modulus))) serverRSAParams = append(serverRSAParams, modulus...) @@ -138,13 +138,8 @@ func (ka *rsaKeyAgreement) processServerKeyExchange(config *Config, clientHello return errServerKeyExchange } rawExponent := k[0:exponentLength] - exponent := 0 - for _, b := range rawExponent { - exponent <<= 8 - exponent |= int(b) - } ka.publicKey = new(rsa.PublicKey) - ka.publicKey.E = exponent + ka.publicKey.E = new(big.Int).SetBytes(rawExponent) ka.publicKey.N = modulus paramsLen := 2 + exponentLength + 2 + modulusLen diff --git a/crypto/x509/parser.go b/crypto/x509/parser.go index d5198ab214..8c3284f2aa 100644 --- a/crypto/x509/parser.go +++ b/crypto/x509/parser.go @@ -242,14 +242,14 @@ func parsePublicKey(keyData *publicKeyInfo) (any, error) { } */ - p := &pkcs1PublicKey{N: new(big.Int)} + p := &pkcs1PublicKey{N: new(big.Int), E: new(big.Int)} if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("x509: invalid RSA public key") } if !der.ReadASN1Integer(p.N) { return nil, errors.New("x509: invalid RSA modulus") } - if !der.ReadASN1Integer(&p.E) { + if !der.ReadASN1Integer(p.E) { return nil, errors.New("x509: invalid RSA public exponent") } @@ -258,7 +258,7 @@ func parsePublicKey(keyData *publicKeyInfo) (any, error) { if p.N.Sign() <= 0 { return nil, errors.New("x509: RSA modulus is not a positive number") } - if p.E <= 0 { + if p.E.Sign() <= 0 { return nil, errors.New("x509: RSA public exponent is not a positive number") } } diff --git a/crypto/x509/pkcs1.go b/crypto/x509/pkcs1.go index a1487f537f..a9e887efcc 100644 --- a/crypto/x509/pkcs1.go +++ b/crypto/x509/pkcs1.go @@ -17,7 +17,7 @@ import ( type pkcs1PrivateKey struct { Version int N *big.Int - E int + E *big.Int D *big.Int P *big.Int Q *big.Int @@ -39,7 +39,7 @@ type pkcs1AdditionalRSAPrime struct { // pkcs1PublicKey reflects the ASN.1 structure of a PKCS #1 public key. type pkcs1PublicKey struct { N *big.Int - E int + E *big.Int } // x509rsacrt, if zero, makes ParsePKCS1PrivateKey ignore and recompute invalid @@ -178,12 +178,10 @@ func ParsePKCS1PublicKey(der []byte) (*rsa.PublicKey, error) { return nil, asn1.SyntaxError{Msg: "trailing data"} } - if pub.N.Sign() <= 0 || pub.E <= 0 { + if pub.N.Sign() <= 0 || pub.E == nil || pub.E.Sign() <= 0 { return nil, errors.New("x509: public key contains zero or negative value") } - if pub.E > 1<<31-1 { - return nil, errors.New("x509: public key contains large public exponent") - } + // ZCrypto - with our fork of crypto/rsa, we allow large public exponents. return &rsa.PublicKey{ E: pub.E, diff --git a/crypto/x509/x509_test.go b/crypto/x509/x509_test.go index 0061a65471..562d79f37e 100644 --- a/crypto/x509/x509_test.go +++ b/crypto/x509/x509_test.go @@ -49,7 +49,7 @@ func TestParsePKCS1PrivateKey(t *testing.T) { return } if priv.PublicKey.N.Cmp(rsaPrivateKey.PublicKey.N) != 0 || - priv.PublicKey.E != rsaPrivateKey.PublicKey.E || + priv.PublicKey.E.Cmp(rsaPrivateKey.PublicKey.E) != 0 || priv.D.Cmp(rsaPrivateKey.D) != 0 || priv.Primes[0].Cmp(rsaPrivateKey.Primes[0]) != 0 || priv.Primes[1].Cmp(rsaPrivateKey.Primes[1]) != 0 { @@ -217,7 +217,7 @@ func bigFromHexString(s string) *big.Int { var rsaPrivateKey = &rsa.PrivateKey{ PublicKey: rsa.PublicKey{ N: bigFromString("124737666279038955318614287965056875799409043964547386061640914307192830334599556034328900586693254156136128122194531292927142396093148164407300419162827624945636708870992355233833321488652786796134504707628792159725681555822420087112284637501705261187690946267527866880072856272532711620639179596808018872997"), - E: 65537, + E: big.NewInt(65537), }, D: bigFromString("69322600686866301945688231018559005300304807960033948687567105312977055197015197977971637657636780793670599180105424702854759606794705928621125408040473426339714144598640466128488132656829419518221592374964225347786430566310906679585739468938549035854760501049443920822523780156843263434219450229353270690889"), Primes: []*big.Int{ @@ -230,7 +230,7 @@ func TestMarshalRSAPrivateKey(t *testing.T) { priv := &rsa.PrivateKey{ PublicKey: rsa.PublicKey{ N: fromBase10("16346378922382193400538269749936049106320265317511766357599732575277382844051791096569333808598921852351577762718529818072849191122419410612033592401403764925096136759934497687765453905884149505175426053037420486697072448609022753683683718057795566811401938833367954642951433473337066311978821180526439641496973296037000052546108507805269279414789035461158073156772151892452251106173507240488993608650881929629163465099476849643165682709047462010581308719577053905787496296934240246311806555924593059995202856826239801816771116902778517096212527979497399966526283516447337775509777558018145573127308919204297111496233"), - E: 3, + E: big.NewInt(3), }, D: fromBase10("10897585948254795600358846499957366070880176878341177571733155050184921896034527397712889205732614568234385175145686545381899460748279607074689061600935843283397424506622998458510302603922766336783617368686090042765718290914099334449154829375179958369993407724946186243249568928237086215759259909861748642124071874879861299389874230489928271621259294894142840428407196932444474088857746123104978617098858619445675532587787023228852383149557470077802718705420275739737958953794088728369933811184572620857678792001136676902250566845618813972833750098806496641114644760255910789397593428910198080271317419213080834885003"), Primes: []*big.Int{ @@ -248,7 +248,7 @@ func TestMarshalRSAPrivateKey(t *testing.T) { return } if priv.PublicKey.N.Cmp(priv2.PublicKey.N) != 0 || - priv.PublicKey.E != priv2.PublicKey.E || + priv.PublicKey.E.Cmp(priv2.PublicKey.E) != 0 || priv.D.Cmp(priv2.D) != 0 || len(priv2.Primes) != 3 || priv.Primes[0].Cmp(priv2.Primes[0]) != 0 || @@ -261,14 +261,14 @@ func TestMarshalRSAPrivateKey(t *testing.T) { func TestMarshalRSAPublicKey(t *testing.T) { pub := &rsa.PublicKey{ N: fromBase10("16346378922382193400538269749936049106320265317511766357599732575277382844051791096569333808598921852351577762718529818072849191122419410612033592401403764925096136759934497687765453905884149505175426053037420486697072448609022753683683718057795566811401938833367954642951433473337066311978821180526439641496973296037000052546108507805269279414789035461158073156772151892452251106173507240488993608650881929629163465099476849643165682709047462010581308719577053905787496296934240246311806555924593059995202856826239801816771116902778517096212527979497399966526283516447337775509777558018145573127308919204297111496233"), - E: 3, + E: big.NewInt(3), } derBytes := MarshalPKCS1PublicKey(pub) pub2, err := ParsePKCS1PublicKey(derBytes) if err != nil { t.Errorf("ParsePKCS1PublicKey: %s", err) } - if pub.N.Cmp(pub2.N) != 0 || pub.E != pub2.E { + if pub.N.Cmp(pub2.N) != 0 || pub.E.Cmp(pub2.E) != 0 { t.Errorf("ParsePKCS1PublicKey = %+v, want %+v", pub, pub2) } @@ -286,7 +286,7 @@ func TestMarshalRSAPublicKey(t *testing.T) { if err != nil { t.Errorf("Unmarshal(rsa.PublicKey): %v", err) } - if len(rest) != 0 || pub.N.Cmp(pub3.N) != 0 || pub.E != pub3.E { + if len(rest) != 0 || pub.N.Cmp(pub3.N) != 0 || pub.E.Cmp(pub3.E) != 0 { t.Errorf("Unmarshal(rsa.PublicKey) = %+v, %q want %+v, %q", pub, rest, pub2, []byte(nil)) } @@ -346,12 +346,9 @@ func TestMarshalRSAPublicKey(t *testing.T) { 0x02, 5, // INTEGER, 5 bytes 0x00, 0x80, 0x00, 0x00, 0x00, }, - // On 64-bit systems, encoding/asn1 will accept the - // public exponent, but ParsePKCS1PublicKey will return - // an error. On 32-bit systems, encoding/asn1 will - // return the error. The common substring of both error - // is the word “large”. - expectedErrSubstr: "large", + // excrypto: with our fork of crypto/rsa, we accept + // large public exponents (E is *big.Int), so this no + // longer errors. }, } diff --git a/x/crypto/openpgp/packet/encrypted_key_test.go b/x/crypto/openpgp/packet/encrypted_key_test.go index 67eeb028a1..3585a93496 100644 --- a/x/crypto/openpgp/packet/encrypted_key_test.go +++ b/x/crypto/openpgp/packet/encrypted_key_test.go @@ -25,7 +25,7 @@ func bigFromBase10(s string) *big.Int { } var encryptedKeyPub = rsa.PublicKey{ - E: 65537, + E: big.NewInt(65537), N: bigFromBase10("115804063926007623305902631768113868327816898845124614648849934718568541074358183759250136204762053879858102352159854352727097033322663029387610959884180306668628526686121021235757016368038585212410610742029286439607686208110250133174279811431933746643015923132833417396844716207301518956640020862630546868823"), } diff --git a/x/crypto/openpgp/packet/public_key.go b/x/crypto/openpgp/packet/public_key.go index dca8b1252d..3fa2870ecb 100644 --- a/x/crypto/openpgp/packet/public_key.go +++ b/x/crypto/openpgp/packet/public_key.go @@ -186,7 +186,7 @@ func NewRSAPublicKey(creationTime time.Time, pub *rsa.PublicKey) *PublicKey { PubKeyAlgo: PubKeyAlgoRSA, PublicKey: pub, n: fromBig(pub.N), - e: fromBig(big.NewInt(int64(pub.E))), + e: fromBig(pub.E), } pk.setFingerPrintAndKeyId() @@ -329,11 +329,7 @@ func (pk *PublicKey) parseRSA(r io.Reader) (err error) { } rsa := &rsa.PublicKey{ N: new(big.Int).SetBytes(pk.n.bytes), - E: 0, - } - for i := 0; i < len(pk.e.bytes); i++ { - rsa.E <<= 8 - rsa.E |= int(pk.e.bytes[i]) + E: new(big.Int).SetBytes(pk.e.bytes), } pk.PublicKey = rsa return diff --git a/x/crypto/openpgp/packet/public_key_v3.go b/x/crypto/openpgp/packet/public_key_v3.go index 35541855a6..69185640b8 100644 --- a/x/crypto/openpgp/packet/public_key_v3.go +++ b/x/crypto/openpgp/packet/public_key_v3.go @@ -43,7 +43,7 @@ func newRSAPublicKeyV3(creationTime time.Time, pub *rsa.PublicKey) *PublicKeyV3 CreationTime: creationTime, PublicKey: pub, n: fromBig(pub.N), - e: fromBig(big.NewInt(int64(pub.E))), + e: fromBig(pub.E), } pk.setFingerPrintAndKeyId() @@ -104,10 +104,9 @@ func (pk *PublicKeyV3) parseRSA(r io.Reader) (err error) { err = errors.UnsupportedError("large public exponent") return } - rsa := &rsa.PublicKey{N: new(big.Int).SetBytes(pk.n.bytes)} - for i := 0; i < len(pk.e.bytes); i++ { - rsa.E <<= 8 - rsa.E |= int(pk.e.bytes[i]) + rsa := &rsa.PublicKey{ + N: new(big.Int).SetBytes(pk.n.bytes), + E: new(big.Int).SetBytes(pk.e.bytes), } pk.PublicKey = rsa return diff --git a/x/crypto/ssh/agent/client.go b/x/crypto/ssh/agent/client.go index d6366175ed..f3f0d78d90 100644 --- a/x/crypto/ssh/agent/client.go +++ b/x/crypto/ssh/agent/client.go @@ -547,7 +547,7 @@ func (c *client) insertKey(s interface{}, comment string, constraints []byte) er req = ssh.Marshal(rsaKeyMsg{ Type: ssh.KeyAlgoRSA, N: k.N, - E: big.NewInt(int64(k.E)), + E: k.E, D: k.D, Iqmp: k.Precomputed.Qinv, P: k.Primes[0], diff --git a/x/crypto/ssh/agent/server.go b/x/crypto/ssh/agent/server.go index 5901d8252f..5f531c87c9 100644 --- a/x/crypto/ssh/agent/server.go +++ b/x/crypto/ssh/agent/server.go @@ -245,12 +245,14 @@ func parseRSAKey(req []byte) (*AddedKey, error) { if err := ssh.Unmarshal(req, &k); err != nil { return nil, err } - if k.E.BitLen() > 30 { - return nil, errors.New("agent: RSA public exponent too large") + if k.E.Sign() <= 0 || k.E.Cmp(big.NewInt(3)) < 0 || k.E.Bit(0) == 0 { + return nil, errors.New("agent: invalid RSA public exponent") } priv := &rsa.PrivateKey{ PublicKey: rsa.PublicKey{ - E: int(k.E.Int64()), + // Defensively copy E so callers cannot mutate it through the + // parsed wire structure. + E: new(big.Int).Set(k.E), N: k.N, }, D: k.D, @@ -393,13 +395,9 @@ func parseRSACert(req []byte) (*AddedKey, error) { return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err) } - if rsaPub.E.BitLen() > 30 { - return nil, errors.New("agent: RSA public exponent too large") - } - priv := rsa.PrivateKey{ PublicKey: rsa.PublicKey{ - E: int(rsaPub.E.Int64()), + E: rsaPub.E, N: rsaPub.N, }, D: k.D, diff --git a/x/crypto/ssh/keys.go b/x/crypto/ssh/keys.go index 2e33bd3083..6accbfb395 100644 --- a/x/crypto/ssh/keys.go +++ b/x/crypto/ssh/keys.go @@ -469,22 +469,19 @@ func parseRSA(in []byte) (out PublicKey, rest []byte, err error) { return nil, nil, err } - if w.E.BitLen() > 24 { - return nil, nil, errors.New("ssh: exponent too large") - } - e := w.E.Int64() - if e < 3 || e&1 == 0 { + if w.E.Cmp(big.NewInt(3)) < 0 || w.E.Bit(0) == 0 { return nil, nil, errors.New("ssh: incorrect exponent") } var key rsa.PublicKey - key.E = int(e) - key.N = w.N + // Defensively copy E and N so subsequent mutations to the parsed wire + // buffer cannot affect the returned key. + key.E = new(big.Int).Set(w.E) + key.N = new(big.Int).Set(w.N) return (*rsaPublicKey)(&key), w.Rest, nil } func (r *rsaPublicKey) Marshal() []byte { - e := new(big.Int).SetInt64(int64(r.E)) // RSA publickey struct layout should match the struct used by // parseRSACert in the x/crypto/ssh/agent package. wirekey := struct { @@ -493,7 +490,7 @@ func (r *rsaPublicKey) Marshal() []byte { N *big.Int }{ KeyAlgoRSA, - e, + r.E, r.N, } return Marshal(&wirekey) @@ -1585,7 +1582,7 @@ func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.Priv pk := &rsa.PrivateKey{ PublicKey: rsa.PublicKey{ N: key.N, - E: int(key.E.Int64()), + E: key.E, }, D: key.D, Primes: []*big.Int{key.P, key.Q}, @@ -1685,7 +1682,7 @@ func marshalOpenSSHPrivateKey(key crypto.PrivateKey, comment string, encrypt ope switch k := key.(type) { case *rsa.PrivateKey: - E := new(big.Int).SetInt64(int64(k.PublicKey.E)) + E := k.PublicKey.E // Marshal public key: // E and N are in reversed order in the public and private key. pubKey := struct {