Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto/rsa: fix RSA mod overlow timing channel #67044

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/crypto/ecdsa/ecdsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ func signNISTEC[Point nistPoint[Point]](c *nistCurve[Point], priv *PrivateKey, c
if err != nil {
return nil, err
}
r, err := bigmod.NewNat().SetOverflowingBytes(Rx, c.N)
r, _, err := bigmod.NewNat().SetOverflowingBytes(Rx, c.N)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -400,7 +400,7 @@ func hashToNat[Point nistPoint[Point]](c *nistCurve[Point], e *bigmod.Nat, hash
}
}
}
_, err := e.SetOverflowingBytes(hash, c.N)
_, _, err := e.SetOverflowingBytes(hash, c.N)
if err != nil {
panic("ecdsa: internal error: truncated hash is too long")
}
Expand Down Expand Up @@ -536,7 +536,7 @@ func verifyNISTEC[Point nistPoint[Point]](c *nistCurve[Point], pub *PublicKey, h
return false
}

v, err := bigmod.NewNat().SetOverflowingBytes(Rx, c.N)
v, _, err := bigmod.NewNat().SetOverflowingBytes(Rx, c.N)
if err != nil {
return false
}
Expand Down
20 changes: 14 additions & 6 deletions src/crypto/internal/bigmod/nat.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package bigmod

import (
"crypto/subtle"
"encoding/binary"
"errors"
"math/big"
Expand Down Expand Up @@ -154,16 +155,22 @@ func (x *Nat) SetBytes(b []byte, m *Modulus) (*Nat, error) {
// reduces overflowing values up to 2^⌈log2(m)⌉ - 1.
//
// The output will be resized to the size of m and overwritten.
func (x *Nat) SetOverflowingBytes(b []byte, m *Modulus) (*Nat, error) {
func (x *Nat) SetOverflowingBytes(b []byte, m *Modulus) (*Nat, choice, error) {
if err := x.setBytes(b, m); err != nil {
return nil, err
return nil, 0, err
}
leading := _W - bitLen(x.limbs[len(x.limbs)-1])
if leading < m.leading {
return nil, errors.New("input overflows the modulus size")
return nil, 0, errors.New("input overflows the modulus size")
}
x.maybeSubtractModulus(no, m)
return x, nil

// If x-m has no underflow then b overflows m (m <= x). In all other cases
// no overflow occurred.
underflow := x.maybeSubtractModulus(no, m)
// We only need to check first byte of underflow because we have already
// checked bitlen(m)>=bitlen(x), thus 0 <= overflow <= 7.
overflowed := choice(subtle.ConstantTimeByteEq(uint8(underflow), uint8(0)))
return x, overflowed, nil
}

// bigEndianUint returns the contents of buf interpreted as a
Expand Down Expand Up @@ -507,13 +514,14 @@ func (out *Nat) resetFor(m *Modulus) *Nat {
// overflowed its size, meaning abstractly x > 2^_W*n > m even if x < m.
//
// x and m operands must have the same announced length.
func (x *Nat) maybeSubtractModulus(always choice, m *Modulus) {
func (x *Nat) maybeSubtractModulus(always choice, m *Modulus) uint {
t := NewNat().set(x)
underflow := t.sub(m.nat)
// We keep the result if x - m didn't underflow (meaning x >= m)
// or if always was set.
keep := not(choice(underflow)) | choice(always)
x.assign(keep, t)
return underflow
}

// Sub computes x = x - y mod m.
Expand Down
8 changes: 5 additions & 3 deletions src/crypto/rsa/pkcs1v15.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,13 +351,15 @@ func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte)
return ErrVerification
}

em, err := encrypt(pub, sig)
if err != nil {
em, ok := timingSafeEncrypt(pub, sig)
// Not checking ok to avoid timing attacks, ok will be checked at the very
// end of the function. See https://golang.org/issue/67043
if em == nil {
return ErrVerification
}
// EM = 0x00 || 0x01 || PS || 0x00 || T

ok := subtle.ConstantTimeByteEq(em[0], 0)
ok &= subtle.ConstantTimeByteEq(em[0], 0)
ok &= subtle.ConstantTimeByteEq(em[1], 1)
ok &= subtle.ConstantTimeCompare(em[k-hashLen:k], hashed)
ok &= subtle.ConstantTimeCompare(em[k-tLen:k-hashLen], prefix)
Expand Down
14 changes: 11 additions & 3 deletions src/crypto/rsa/pss.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte,
// result of hashing the input message using the given hash function. The opts
// argument may be nil, in which case sensible defaults are used. opts.Hash is
// ignored.
//
// Note: As neither the signature nor the public key is viewed as secret;
// VerifyPSS does not provide confidentiality guarantees on the public key or
// signature.
func VerifyPSS(pub *PublicKey, hash crypto.Hash, digest []byte, sig []byte, opts *PSSOptions) error {
if boring.Enabled {
bkey, err := boringPublicKey(pub)
Expand All @@ -361,8 +365,8 @@ func VerifyPSS(pub *PublicKey, hash crypto.Hash, digest []byte, sig []byte, opts

emBits := pub.N.BitLen() - 1
emLen := (emBits + 7) / 8
em, err := encrypt(pub, sig)
if err != nil {
em, ok := timingSafeEncrypt(pub, sig)
if em == nil {
return ErrVerification
}

Expand All @@ -378,5 +382,9 @@ func VerifyPSS(pub *PublicKey, hash crypto.Hash, digest []byte, sig []byte, opts
em = em[1:]
}

return emsaPSSVerify(digest, em, emBits, opts.saltLength(), hash.New())
err := emsaPSSVerify(digest, em, emBits, opts.saltLength(), hash.New())
if err != nil || ok != 1 {
return ErrVerification
}
return nil
}
30 changes: 27 additions & 3 deletions src/crypto/rsa/rsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
// Decrypter and Signer interfaces from the crypto package.
//
// Operations in this package are implemented using constant-time algorithms,
// except for [GenerateKey], [PrivateKey.Precompute], and [PrivateKey.Validate].
// Every other operation only leaks the bit size of the involved values, which
// all depend on the selected key size.
// except for encrypt, [GenerateKey], [PrivateKey.Precompute],
// and [PrivateKey.Validate]. Every other operation only leaks the bit size of
// the involved values, which all depend on the selected key size.
package rsa

import (
Expand Down Expand Up @@ -479,6 +479,11 @@ func mgf1XOR(out []byte, hash hash.Hash, seed []byte) {
// be returned if the size of the salt is too large.
var ErrMessageTooLong = errors.New("crypto/rsa: message too long for RSA key size")

// WARNING: encrypt contains a timing channel that reveals if the signature
// is numerically larger than the modulus N, potentially leaking a bit of
// the public key or the plaintext. See https://golang.org/issue/67043
//
// To avoid this timing channel use timeSafeEncrypt instead
func encrypt(pub *PublicKey, plaintext []byte) ([]byte, error) {
boring.Unreachable()

Expand All @@ -495,6 +500,25 @@ func encrypt(pub *PublicKey, plaintext []byte) ([]byte, error) {
return bigmod.NewNat().ExpShortVarTime(m, e, N).Bytes(N), nil
}

// timingSafeEncrypt performs the same operation as encrypt but doesn't
// leak information about the signature or public key via a timing channel.
func timingSafeEncrypt(pub *PublicKey, plaintext []byte) ([]byte, int) {
boring.Unreachable()

N, err := bigmod.NewModulusFromBig(pub.N)
if err != nil {
return nil, 0
}
m, overflowed, err := bigmod.NewNat().SetOverflowingBytes(plaintext, N)
if err != nil {
return nil, 0
}
e := uint(pub.E)

ok := int(overflowed ^ 1)
return bigmod.NewNat().ExpShortVarTime(m, e, N).Bytes(N), ok
}

// EncryptOAEP encrypts the given message with RSA-OAEP.
//
// OAEP is parameterised by a hash function that is used as a random oracle.
Expand Down