]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/ecdsa: move implementation to crypto/internal/fips/ecdsa
authorFilippo Valsorda <filippo@golang.org>
Sat, 16 Nov 2024 15:38:07 +0000 (16:38 +0100)
committerGopher Robot <gobot@golang.org>
Tue, 19 Nov 2024 23:01:44 +0000 (23:01 +0000)
For #69536

Change-Id: I8794d75c11cdadd91e420541b26af35e62006af4
Reviewed-on: https://go-review.googlesource.com/c/go/+/628677
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/crypto/ecdsa/ecdsa.go
src/crypto/ecdsa/ecdsa_test.go
src/crypto/internal/fips/ecdsa/ecdsa.go [new file with mode: 0644]
src/crypto/internal/fips/ecdsa/ecdsa_test.go [new file with mode: 0644]
src/go/build/deps_test.go

index 0973f8209847038ef6088627161d27ae8df10caf..534512bcba6ec52378ac78753493db572e3e274c 100644 (file)
@@ -24,7 +24,6 @@ package ecdsa
 // [SEC 1, Version 2.0]: https://www.secg.org/sec1-v2.pdf
 
 import (
-       "bytes"
        "crypto"
        "crypto/aes"
        "crypto/cipher"
@@ -32,15 +31,13 @@ import (
        "crypto/elliptic"
        "crypto/internal/boring"
        "crypto/internal/boring/bbig"
-       "crypto/internal/fips/bigmod"
-       "crypto/internal/fips/nistec"
+       "crypto/internal/fips/ecdsa"
        "crypto/internal/randutil"
        "crypto/sha512"
        "crypto/subtle"
        "errors"
        "io"
        "math/big"
-       "sync"
 
        "golang.org/x/crypto/cryptobyte"
        "golang.org/x/crypto/cryptobyte/asn1"
@@ -173,78 +170,26 @@ func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) {
 
        switch c.Params() {
        case elliptic.P224().Params():
-               return generateNISTEC(p224(), rand)
+               return generateFIPS(c, ecdsa.P224(), rand)
        case elliptic.P256().Params():
-               return generateNISTEC(p256(), rand)
+               return generateFIPS(c, ecdsa.P256(), rand)
        case elliptic.P384().Params():
-               return generateNISTEC(p384(), rand)
+               return generateFIPS(c, ecdsa.P384(), rand)
        case elliptic.P521().Params():
-               return generateNISTEC(p521(), rand)
+               return generateFIPS(c, ecdsa.P521(), rand)
        default:
                return generateLegacy(c, rand)
        }
 }
 
-func generateNISTEC[Point nistPoint[Point]](c *nistCurve[Point], rand io.Reader) (*PrivateKey, error) {
-       k, Q, err := randomPoint(c, rand)
+func generateFIPS[P ecdsa.Point[P]](curve elliptic.Curve, c *ecdsa.Curve[P], rand io.Reader) (*PrivateKey, error) {
+       privateKey, err := ecdsa.GenerateKey(c, rand)
        if err != nil {
                return nil, err
        }
-
-       priv := new(PrivateKey)
-       priv.PublicKey.Curve = c.curve
-       priv.D = new(big.Int).SetBytes(k.Bytes(c.N))
-       priv.PublicKey.X, priv.PublicKey.Y, err = c.pointToAffine(Q)
-       if err != nil {
-               return nil, err
-       }
-       return priv, nil
+       return privateKeyFromFIPS(curve, privateKey)
 }
 
-// randomPoint returns a random scalar and the corresponding point using the
-// procedure given in FIPS 186-4, Appendix B.5.2 (rejection sampling).
-func randomPoint[Point nistPoint[Point]](c *nistCurve[Point], rand io.Reader) (k *bigmod.Nat, p Point, err error) {
-       k = bigmod.NewNat()
-       for {
-               b := make([]byte, c.N.Size())
-               if _, err = io.ReadFull(rand, b); err != nil {
-                       return
-               }
-
-               // Mask off any excess bits to increase the chance of hitting a value in
-               // (0, N). These are the most dangerous lines in the package and maybe in
-               // the library: a single bit of bias in the selection of nonces would likely
-               // lead to key recovery, but no tests would fail. Look but DO NOT TOUCH.
-               if excess := len(b)*8 - c.N.BitLen(); excess > 0 {
-                       // Just to be safe, assert that this only happens for the one curve that
-                       // doesn't have a round number of bits.
-                       if excess != 0 && c.curve.Params().Name != "P-521" {
-                               panic("ecdsa: internal error: unexpectedly masking off bits")
-                       }
-                       b[0] >>= excess
-               }
-
-               // FIPS 186-4 makes us check k <= N - 2 and then add one.
-               // Checking 0 < k <= N - 1 is strictly equivalent.
-               // None of this matters anyway because the chance of selecting
-               // zero is cryptographically negligible.
-               if _, err = k.SetBytes(b, c.N); err == nil && k.IsZero() == 0 {
-                       break
-               }
-
-               if testingOnlyRejectionSamplingLooped != nil {
-                       testingOnlyRejectionSamplingLooped()
-               }
-       }
-
-       p, err = c.newPoint().ScalarBaseMult(k.Bytes(c.N))
-       return
-}
-
-// testingOnlyRejectionSamplingLooped is called when rejection sampling in
-// randomPoint rejects a candidate for being higher than the modulus.
-var testingOnlyRejectionSamplingLooped func()
-
 // errNoAsm is returned by signAsm and verifyAsm when the assembly
 // implementation is not available.
 var errNoAsm = errors.New("no assembly implementation available")
@@ -280,63 +225,28 @@ func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) {
 
        switch priv.Curve.Params() {
        case elliptic.P224().Params():
-               return signNISTEC(p224(), priv, csprng, hash)
+               return signFIPS(ecdsa.P224(), priv, csprng, hash)
        case elliptic.P256().Params():
-               return signNISTEC(p256(), priv, csprng, hash)
+               return signFIPS(ecdsa.P256(), priv, csprng, hash)
        case elliptic.P384().Params():
-               return signNISTEC(p384(), priv, csprng, hash)
+               return signFIPS(ecdsa.P384(), priv, csprng, hash)
        case elliptic.P521().Params():
-               return signNISTEC(p521(), priv, csprng, hash)
+               return signFIPS(ecdsa.P521(), priv, csprng, hash)
        default:
                return signLegacy(priv, csprng, hash)
        }
 }
 
-func signNISTEC[Point nistPoint[Point]](c *nistCurve[Point], priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, err error) {
-       // SEC 1, Version 2.0, Section 4.1.3
-
-       k, R, err := randomPoint(c, csprng)
+func signFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey, csprng io.Reader, hash []byte) ([]byte, error) {
+       k, err := privateKeyToFIPS(c, priv)
        if err != nil {
                return nil, err
        }
-
-       // kInv = k⁻¹
-       kInv := bigmod.NewNat()
-       inverse(c, kInv, k)
-
-       Rx, err := R.BytesX()
+       sig, err := ecdsa.Sign(c, k, csprng, hash)
        if err != nil {
                return nil, err
        }
-       r, err := bigmod.NewNat().SetOverflowingBytes(Rx, c.N)
-       if err != nil {
-               return nil, err
-       }
-
-       // The spec wants us to retry here, but the chance of hitting this condition
-       // on a large prime-order group like the NIST curves we support is
-       // cryptographically negligible. If we hit it, something is awfully wrong.
-       if r.IsZero() == 1 {
-               return nil, errors.New("ecdsa: internal error: r is zero")
-       }
-
-       e := bigmod.NewNat()
-       hashToNat(c, e, hash)
-
-       s, err := bigmod.NewNat().SetBytes(priv.D.Bytes(), c.N)
-       if err != nil {
-               return nil, err
-       }
-       s.Mul(r, c.N)
-       s.Add(e, c.N)
-       s.Mul(kInv, c.N)
-
-       // Again, the chance of this happening is cryptographically negligible.
-       if s.IsZero() == 1 {
-               return nil, errors.New("ecdsa: internal error: s is zero")
-       }
-
-       return encodeSignature(r.Bytes(c.N), s.Bytes(c.N))
+       return encodeSignature(sig.R, sig.S)
 }
 
 func encodeSignature(r, s []byte) ([]byte, error) {
@@ -366,50 +276,6 @@ func addASN1IntBytes(b *cryptobyte.Builder, bytes []byte) {
        })
 }
 
-// inverse sets kInv to the inverse of k modulo the order of the curve.
-func inverse[Point nistPoint[Point]](c *nistCurve[Point], kInv, k *bigmod.Nat) {
-       if c.curve.Params().Name == "P-256" {
-               kBytes, err := nistec.P256OrdInverse(k.Bytes(c.N))
-               // Some platforms don't implement P256OrdInverse, and always return an error.
-               if err == nil {
-                       _, err := kInv.SetBytes(kBytes, c.N)
-                       if err != nil {
-                               panic("ecdsa: internal error: P256OrdInverse produced an invalid value")
-                       }
-                       return
-               }
-       }
-
-       // Calculate the inverse of s in GF(N) using Fermat's method
-       // (exponentiation modulo P - 2, per Euler's theorem)
-       kInv.Exp(k, c.nMinus2, c.N)
-}
-
-// hashToNat sets e to the left-most bits of hash, according to
-// SEC 1, Section 4.1.3, point 5 and Section 4.1.4, point 3.
-func hashToNat[Point nistPoint[Point]](c *nistCurve[Point], e *bigmod.Nat, hash []byte) {
-       // ECDSA asks us to take the left-most log2(N) bits of hash, and use them as
-       // an integer modulo N. This is the absolute worst of all worlds: we still
-       // have to reduce, because the result might still overflow N, but to take
-       // the left-most bits for P-521 we have to do a right shift.
-       if size := c.N.Size(); len(hash) >= size {
-               hash = hash[:size]
-               if excess := len(hash)*8 - c.N.BitLen(); excess > 0 {
-                       hash = bytes.Clone(hash)
-                       for i := len(hash) - 1; i >= 0; i-- {
-                               hash[i] >>= excess
-                               if i > 0 {
-                                       hash[i] |= hash[i-1] << (8 - excess)
-                               }
-                       }
-               }
-       }
-       _, err := e.SetOverflowingBytes(hash, c.N)
-       if err != nil {
-               panic("ecdsa: internal error: truncated hash is too long")
-       }
-}
-
 // mixedCSPRNG returns a CSPRNG that mixes entropy from rand with the message
 // and the private key, to protect the key in case rand fails. This is
 // equivalent in security to RFC 6979 deterministic nonce generation, but still
@@ -486,69 +352,31 @@ func VerifyASN1(pub *PublicKey, hash, sig []byte) bool {
 
        switch pub.Curve.Params() {
        case elliptic.P224().Params():
-               return verifyNISTEC(p224(), pub, hash, sig)
+               return verifyFIPS(ecdsa.P224(), pub, hash, sig)
        case elliptic.P256().Params():
-               return verifyNISTEC(p256(), pub, hash, sig)
+               return verifyFIPS(ecdsa.P256(), pub, hash, sig)
        case elliptic.P384().Params():
-               return verifyNISTEC(p384(), pub, hash, sig)
+               return verifyFIPS(ecdsa.P384(), pub, hash, sig)
        case elliptic.P521().Params():
-               return verifyNISTEC(p521(), pub, hash, sig)
+               return verifyFIPS(ecdsa.P521(), pub, hash, sig)
        default:
                return verifyLegacy(pub, hash, sig)
        }
 }
 
-func verifyNISTEC[Point nistPoint[Point]](c *nistCurve[Point], pub *PublicKey, hash, sig []byte) bool {
-       rBytes, sBytes, err := parseSignature(sig)
+func verifyFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], pub *PublicKey, hash, sig []byte) bool {
+       rs, err := parseSignature(sig)
        if err != nil {
                return false
        }
-
-       Q, err := c.pointFromAffine(pub.X, pub.Y)
+       k, err := publicKeyToFIPS(c, pub)
        if err != nil {
                return false
        }
-
-       // SEC 1, Version 2.0, Section 4.1.4
-
-       r, err := bigmod.NewNat().SetBytes(rBytes, c.N)
-       if err != nil || r.IsZero() == 1 {
+       if err := ecdsa.Verify(c, k, hash, &ecdsa.Signature{R: r, S: s}); err != nil {
                return false
        }
-       s, err := bigmod.NewNat().SetBytes(sBytes, c.N)
-       if err != nil || s.IsZero() == 1 {
-               return false
-       }
-
-       e := bigmod.NewNat()
-       hashToNat(c, e, hash)
-
-       // w = s⁻¹
-       w := bigmod.NewNat()
-       inverse(c, w, s)
-
-       // p₁ = [e * s⁻¹]G
-       p1, err := c.newPoint().ScalarBaseMult(e.Mul(w, c.N).Bytes(c.N))
-       if err != nil {
-               return false
-       }
-       // p₂ = [r * s⁻¹]Q
-       p2, err := Q.ScalarMult(Q, w.Mul(r, c.N).Bytes(c.N))
-       if err != nil {
-               return false
-       }
-       // BytesX returns an error for the point at infinity.
-       Rx, err := p1.Add(p1, p2).BytesX()
-       if err != nil {
-               return false
-       }
-
-       v, err := bigmod.NewNat().SetOverflowingBytes(Rx, c.N)
-       if err != nil {
-               return false
-       }
-
-       return v.Equal(r) == 1
+       return true
 }
 
 func parseSignature(sig []byte) (r, s []byte, err error) {
@@ -564,32 +392,47 @@ func parseSignature(sig []byte) (r, s []byte, err error) {
        return r, s, nil
 }
 
-type nistCurve[Point nistPoint[Point]] struct {
-       newPoint func() Point
-       curve    elliptic.Curve
-       N        *bigmod.Modulus
-       nMinus2  []byte
+func publicKeyFromFIPS(curve elliptic.Curve, pub *ecdsa.PublicKey) (*PublicKey, error) {
+       x, y, err := pointToAffine(curve, pub.Bytes())
+       if err != nil {
+               return nil, err
+       }
+       return &PublicKey{Curve: curve, X: x, Y: y}, nil
+}
+
+func privateKeyFromFIPS(curve elliptic.Curve, priv *ecdsa.PrivateKey) (*PrivateKey, error) {
+       pub, err := publicKeyFromFIPS(curve, priv.PublicKey())
+       if err != nil {
+               return nil, err
+       }
+       return &PrivateKey{PublicKey: *pub, D: new(big.Int).SetBytes(priv.Bytes())}, nil
 }
 
-// nistPoint is a generic constraint for the nistec Point types.
-type nistPoint[T any] interface {
-       Bytes() []byte
-       BytesX() ([]byte, error)
-       SetBytes([]byte) (T, error)
-       Add(T, T) T
-       ScalarMult(T, []byte) (T, error)
-       ScalarBaseMult([]byte) (T, error)
+func publicKeyToFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], pub *PublicKey) (*ecdsa.PublicKey, error) {
+       Q, err := pointFromAffine(pub.Curve, pub.X, pub.Y)
+       if err != nil {
+               return nil, err
+       }
+       return ecdsa.NewPublicKey(c, Q)
+}
+
+func privateKeyToFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey) (*ecdsa.PrivateKey, error) {
+       Q, err := pointFromAffine(priv.Curve, priv.X, priv.Y)
+       if err != nil {
+               return nil, err
+       }
+       return ecdsa.NewPrivateKey(c, priv.D.Bytes(), Q)
 }
 
-// pointFromAffine is used to convert the PublicKey to a nistec Point.
-func (curve *nistCurve[Point]) pointFromAffine(x, y *big.Int) (p Point, err error) {
-       bitSize := curve.curve.Params().BitSize
+// pointFromAffine is used to convert the PublicKey to a nistec SetBytes input.
+func pointFromAffine(curve elliptic.Curve, x, y *big.Int) ([]byte, error) {
+       bitSize := curve.Params().BitSize
        // Reject values that would not get correctly encoded.
        if x.Sign() < 0 || y.Sign() < 0 {
-               return p, errors.New("negative coordinate")
+               return nil, errors.New("negative coordinate")
        }
        if x.BitLen() > bitSize || y.BitLen() > bitSize {
-               return p, errors.New("overflowing coordinate")
+               return nil, errors.New("overflowing coordinate")
        }
        // Encode the coordinates and let SetBytes reject invalid points.
        byteLen := (bitSize + 7) / 8
@@ -597,81 +440,17 @@ func (curve *nistCurve[Point]) pointFromAffine(x, y *big.Int) (p Point, err erro
        buf[0] = 4 // uncompressed point
        x.FillBytes(buf[1 : 1+byteLen])
        y.FillBytes(buf[1+byteLen : 1+2*byteLen])
-       return curve.newPoint().SetBytes(buf)
+       return buf, nil
 }
 
-// pointToAffine is used to convert a nistec Point to a PublicKey.
-func (curve *nistCurve[Point]) pointToAffine(p Point) (x, y *big.Int, err error) {
-       out := p.Bytes()
-       if len(out) == 1 && out[0] == 0 {
+// pointToAffine is used to convert a nistec Bytes encoding to a PublicKey.
+func pointToAffine(curve elliptic.Curve, p []byte) (x, y *big.Int, err error) {
+       if len(p) == 1 && p[0] == 0 {
                // This is the encoding of the point at infinity.
                return nil, nil, errors.New("ecdsa: public key point is the infinity")
        }
-       byteLen := (curve.curve.Params().BitSize + 7) / 8
-       x = new(big.Int).SetBytes(out[1 : 1+byteLen])
-       y = new(big.Int).SetBytes(out[1+byteLen:])
+       byteLen := (curve.Params().BitSize + 7) / 8
+       x = new(big.Int).SetBytes(p[1 : 1+byteLen])
+       y = new(big.Int).SetBytes(p[1+byteLen:])
        return x, y, nil
 }
-
-var p224Once sync.Once
-var _p224 *nistCurve[*nistec.P224Point]
-
-func p224() *nistCurve[*nistec.P224Point] {
-       p224Once.Do(func() {
-               _p224 = &nistCurve[*nistec.P224Point]{
-                       newPoint: func() *nistec.P224Point { return nistec.NewP224Point() },
-               }
-               precomputeParams(_p224, elliptic.P224())
-       })
-       return _p224
-}
-
-var p256Once sync.Once
-var _p256 *nistCurve[*nistec.P256Point]
-
-func p256() *nistCurve[*nistec.P256Point] {
-       p256Once.Do(func() {
-               _p256 = &nistCurve[*nistec.P256Point]{
-                       newPoint: func() *nistec.P256Point { return nistec.NewP256Point() },
-               }
-               precomputeParams(_p256, elliptic.P256())
-       })
-       return _p256
-}
-
-var p384Once sync.Once
-var _p384 *nistCurve[*nistec.P384Point]
-
-func p384() *nistCurve[*nistec.P384Point] {
-       p384Once.Do(func() {
-               _p384 = &nistCurve[*nistec.P384Point]{
-                       newPoint: func() *nistec.P384Point { return nistec.NewP384Point() },
-               }
-               precomputeParams(_p384, elliptic.P384())
-       })
-       return _p384
-}
-
-var p521Once sync.Once
-var _p521 *nistCurve[*nistec.P521Point]
-
-func p521() *nistCurve[*nistec.P521Point] {
-       p521Once.Do(func() {
-               _p521 = &nistCurve[*nistec.P521Point]{
-                       newPoint: func() *nistec.P521Point { return nistec.NewP521Point() },
-               }
-               precomputeParams(_p521, elliptic.P521())
-       })
-       return _p521
-}
-
-func precomputeParams[Point nistPoint[Point]](c *nistCurve[Point], curve elliptic.Curve) {
-       params := curve.Params()
-       c.curve = curve
-       var err error
-       c.N, err = bigmod.NewModulus(params.N.Bytes())
-       if err != nil {
-               panic(err)
-       }
-       c.nMinus2 = new(big.Int).Sub(params.N, big.NewInt(2)).Bytes()
-}
index 25ccc52dad51b4d10dae899f36e96cf40e34e12d..5788fee3a053b5daf108efdf4b43ed734680b79f 100644 (file)
@@ -6,10 +6,8 @@ package ecdsa
 
 import (
        "bufio"
-       "bytes"
        "compress/bzip2"
        "crypto/elliptic"
-       "crypto/internal/fips/bigmod"
        "crypto/rand"
        "crypto/sha1"
        "crypto/sha256"
@@ -339,80 +337,6 @@ func testZeroHashSignature(t *testing.T, curve elliptic.Curve) {
        }
 }
 
-func TestRandomPoint(t *testing.T) {
-       t.Run("P-224", func(t *testing.T) { testRandomPoint(t, p224()) })
-       t.Run("P-256", func(t *testing.T) { testRandomPoint(t, p256()) })
-       t.Run("P-384", func(t *testing.T) { testRandomPoint(t, p384()) })
-       t.Run("P-521", func(t *testing.T) { testRandomPoint(t, p521()) })
-}
-
-func testRandomPoint[Point nistPoint[Point]](t *testing.T, c *nistCurve[Point]) {
-       t.Cleanup(func() { testingOnlyRejectionSamplingLooped = nil })
-       var loopCount int
-       testingOnlyRejectionSamplingLooped = func() { loopCount++ }
-
-       // A sequence of all ones will generate 2^N-1, which should be rejected.
-       // (Unless, for example, we are masking too many bits.)
-       r := io.MultiReader(bytes.NewReader(bytes.Repeat([]byte{0xff}, 100)), rand.Reader)
-       if k, p, err := randomPoint(c, r); err != nil {
-               t.Fatal(err)
-       } else if k.IsZero() == 1 {
-               t.Error("k is zero")
-       } else if p.Bytes()[0] != 4 {
-               t.Error("p is infinity")
-       }
-       if loopCount == 0 {
-               t.Error("overflow was not rejected")
-       }
-       loopCount = 0
-
-       // A sequence of all zeroes will generate zero, which should be rejected.
-       r = io.MultiReader(bytes.NewReader(bytes.Repeat([]byte{0}, 100)), rand.Reader)
-       if k, p, err := randomPoint(c, r); err != nil {
-               t.Fatal(err)
-       } else if k.IsZero() == 1 {
-               t.Error("k is zero")
-       } else if p.Bytes()[0] != 4 {
-               t.Error("p is infinity")
-       }
-       if loopCount == 0 {
-               t.Error("zero was not rejected")
-       }
-       loopCount = 0
-
-       // P-256 has a 2⁻³² chance or randomly hitting a rejection. For P-224 it's
-       // 2⁻¹¹², for P-384 it's 2⁻¹⁹⁴, and for P-521 it's 2⁻²⁶², so if we hit in
-       // tests, something is horribly wrong. (For example, we are masking the
-       // wrong bits.)
-       if c.curve == elliptic.P256() {
-               return
-       }
-       if k, p, err := randomPoint(c, rand.Reader); err != nil {
-               t.Fatal(err)
-       } else if k.IsZero() == 1 {
-               t.Error("k is zero")
-       } else if p.Bytes()[0] != 4 {
-               t.Error("p is infinity")
-       }
-       if loopCount > 0 {
-               t.Error("unexpected rejection")
-       }
-}
-
-func TestHashToNat(t *testing.T) {
-       t.Run("P-224", func(t *testing.T) { testHashToNat(t, p224()) })
-       t.Run("P-256", func(t *testing.T) { testHashToNat(t, p256()) })
-       t.Run("P-384", func(t *testing.T) { testHashToNat(t, p384()) })
-       t.Run("P-521", func(t *testing.T) { testHashToNat(t, p521()) })
-}
-
-func testHashToNat[Point nistPoint[Point]](t *testing.T, c *nistCurve[Point]) {
-       for l := 0; l < 600; l++ {
-               h := bytes.Repeat([]byte{0xff}, l)
-               hashToNat(c, bigmod.NewNat(), h)
-       }
-}
-
 func TestZeroSignature(t *testing.T) {
        testAllCurves(t, testZeroSignature)
 }
@@ -494,25 +418,6 @@ func testRMinusNSignature(t *testing.T, curve elliptic.Curve) {
        }
 }
 
-func randomPointForCurve(curve elliptic.Curve, rand io.Reader) error {
-       switch curve.Params() {
-       case elliptic.P224().Params():
-               _, _, err := randomPoint(p224(), rand)
-               return err
-       case elliptic.P256().Params():
-               _, _, err := randomPoint(p256(), rand)
-               return err
-       case elliptic.P384().Params():
-               _, _, err := randomPoint(p384(), rand)
-               return err
-       case elliptic.P521().Params():
-               _, _, err := randomPoint(p521(), rand)
-               return err
-       default:
-               panic("unknown curve")
-       }
-}
-
 func benchmarkAllCurves(b *testing.B, f func(*testing.B, elliptic.Curve)) {
        tests := []struct {
                name  string
diff --git a/src/crypto/internal/fips/ecdsa/ecdsa.go b/src/crypto/internal/fips/ecdsa/ecdsa.go
new file mode 100644 (file)
index 0000000..5b4cf8a
--- /dev/null
@@ -0,0 +1,416 @@
+// Copyright 2024 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 ecdsa
+
+import (
+       "bytes"
+       "crypto/internal/fips/bigmod"
+       "crypto/internal/fips/nistec"
+       "errors"
+       "io"
+       "sync"
+)
+
+// PrivateKey and PublicKey are not generic to make it possible to use them
+// in other types without instantiating them with a specific point type.
+// They are tied to one of the Curve types below through the curveID field.
+
+type PrivateKey struct {
+       pub PublicKey
+       d   []byte // bigmod.(*Nat).Bytes output (fixed length)
+}
+
+func (priv *PrivateKey) Bytes() []byte {
+       return priv.d
+}
+
+func (priv *PrivateKey) PublicKey() *PublicKey {
+       return &priv.pub
+}
+
+type PublicKey struct {
+       curve curveID
+       q     []byte // uncompressed nistec Point.Bytes output
+}
+
+func (pub *PublicKey) Bytes() []byte {
+       return pub.q
+}
+
+type curveID string
+
+const (
+       p224 curveID = "P-224"
+       p256 curveID = "P-256"
+       p384 curveID = "P-384"
+       p521 curveID = "P-521"
+)
+
+type Curve[P Point[P]] struct {
+       curve      curveID
+       newPoint   func() P
+       ordInverse func([]byte) ([]byte, error)
+       N          *bigmod.Modulus
+       nMinus2    []byte
+}
+
+// Point is a generic constraint for the [nistec] Point types.
+type Point[P any] interface {
+       *nistec.P224Point | *nistec.P256Point | *nistec.P384Point | *nistec.P521Point
+       Bytes() []byte
+       BytesX() ([]byte, error)
+       SetBytes([]byte) (P, error)
+       ScalarMult(P, []byte) (P, error)
+       ScalarBaseMult([]byte) (P, error)
+       Add(p1, p2 P) P
+}
+
+func precomputeParams[P Point[P]](c *Curve[P], order []byte) {
+       var err error
+       c.N, err = bigmod.NewModulus(order)
+       if err != nil {
+               panic(err)
+       }
+       two, _ := bigmod.NewNat().SetBytes([]byte{2}, c.N)
+       c.nMinus2 = bigmod.NewNat().ExpandFor(c.N).Sub(two, c.N).Bytes(c.N)
+}
+
+func P224() *Curve[*nistec.P224Point] { return _P224() }
+
+var _P224 = sync.OnceValue(func() *Curve[*nistec.P224Point] {
+       c := &Curve[*nistec.P224Point]{
+               curve:    p224,
+               newPoint: nistec.NewP224Point,
+       }
+       precomputeParams(c, p224Order)
+       return c
+})
+
+var p224Order = []byte{
+       0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+       0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2,
+       0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45,
+       0x5c, 0x5c, 0x2a, 0x3d,
+}
+
+func P256() *Curve[*nistec.P256Point] { return _P256() }
+
+var _P256 = sync.OnceValue(func() *Curve[*nistec.P256Point] {
+       c := &Curve[*nistec.P256Point]{
+               curve:      p256,
+               newPoint:   nistec.NewP256Point,
+               ordInverse: nistec.P256OrdInverse,
+       }
+       precomputeParams(c, p256Order)
+       return c
+})
+
+var p256Order = []byte{
+       0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+       0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+       0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84,
+       0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51}
+
+func P384() *Curve[*nistec.P384Point] { return _P384() }
+
+var _P384 = sync.OnceValue(func() *Curve[*nistec.P384Point] {
+       c := &Curve[*nistec.P384Point]{
+               curve:    p384,
+               newPoint: nistec.NewP384Point,
+       }
+       precomputeParams(c, p384Order)
+       return c
+})
+
+var p384Order = []byte{
+       0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+       0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+       0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+       0xc7, 0x63, 0x4d, 0x81, 0xf4, 0x37, 0x2d, 0xdf,
+       0x58, 0x1a, 0x0d, 0xb2, 0x48, 0xb0, 0xa7, 0x7a,
+       0xec, 0xec, 0x19, 0x6a, 0xcc, 0xc5, 0x29, 0x73}
+
+func P521() *Curve[*nistec.P521Point] { return _P521() }
+
+var _P521 = sync.OnceValue(func() *Curve[*nistec.P521Point] {
+       c := &Curve[*nistec.P521Point]{
+               curve:    p521,
+               newPoint: nistec.NewP521Point,
+       }
+       precomputeParams(c, p521Order)
+       return c
+})
+
+var p521Order = []byte{0x01, 0xff,
+       0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+       0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+       0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+       0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa,
+       0x51, 0x86, 0x87, 0x83, 0xbf, 0x2f, 0x96, 0x6b,
+       0x7f, 0xcc, 0x01, 0x48, 0xf7, 0x09, 0xa5, 0xd0,
+       0x3b, 0xb5, 0xc9, 0xb8, 0x89, 0x9c, 0x47, 0xae,
+       0xbb, 0x6f, 0xb7, 0x1e, 0x91, 0x38, 0x64, 0x09}
+
+func NewPrivateKey[P Point[P]](c *Curve[P], D, Q []byte) (*PrivateKey, error) {
+       _, err := c.newPoint().SetBytes(Q)
+       if err != nil {
+               return nil, err
+       }
+       d, err := bigmod.NewNat().SetBytes(D, c.N)
+       if err != nil {
+               return nil, err
+       }
+       return &PrivateKey{
+               pub: PublicKey{
+                       curve: c.curve,
+                       q:     Q,
+               },
+               d: d.Bytes(c.N),
+       }, nil
+}
+
+func NewPublicKey[P Point[P]](c *Curve[P], Q []byte) (*PublicKey, error) {
+       _, err := c.newPoint().SetBytes(Q)
+       if err != nil {
+               return nil, err
+       }
+       return &PublicKey{
+               curve: c.curve,
+               q:     Q,
+       }, nil
+}
+
+// GenerateKey generates a new ECDSA private key pair for the specified curve.
+func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) {
+       k, Q, err := randomPoint(c, rand)
+       if err != nil {
+               return nil, err
+       }
+       return &PrivateKey{
+               pub: PublicKey{
+                       curve: c.curve,
+                       q:     Q.Bytes(),
+               },
+               d: k.Bytes(c.N),
+       }, nil
+}
+
+// randomPoint returns a random scalar and the corresponding point using the
+// procedure given in FIPS 186-4, Appendix B.5.2 (rejection sampling).
+func randomPoint[P Point[P]](c *Curve[P], rand io.Reader) (k *bigmod.Nat, p P, err error) {
+       k = bigmod.NewNat()
+       for {
+               b := make([]byte, c.N.Size())
+               if _, err = io.ReadFull(rand, b); err != nil {
+                       return
+               }
+
+               // Mask off any excess bits to increase the chance of hitting a value in
+               // (0, N). These are the most dangerous lines in the package and maybe in
+               // the library: a single bit of bias in the selection of nonces would likely
+               // lead to key recovery, but no tests would fail. Look but DO NOT TOUCH.
+               if excess := len(b)*8 - c.N.BitLen(); excess > 0 {
+                       // Just to be safe, assert that this only happens for the one curve that
+                       // doesn't have a round number of bits.
+                       if excess != 0 && c.curve != p521 {
+                               panic("ecdsa: internal error: unexpectedly masking off bits")
+                       }
+                       b[0] >>= excess
+               }
+
+               // FIPS 186-4 makes us check k <= N - 2 and then add one.
+               // Checking 0 < k <= N - 1 is strictly equivalent.
+               // None of this matters anyway because the chance of selecting
+               // zero is cryptographically negligible.
+               if _, err = k.SetBytes(b, c.N); err == nil && k.IsZero() == 0 {
+                       break
+               }
+
+               if testingOnlyRejectionSamplingLooped != nil {
+                       testingOnlyRejectionSamplingLooped()
+               }
+       }
+
+       p, err = c.newPoint().ScalarBaseMult(k.Bytes(c.N))
+       return
+}
+
+// testingOnlyRejectionSamplingLooped is called when rejection sampling in
+// randomPoint rejects a candidate for being higher than the modulus.
+var testingOnlyRejectionSamplingLooped func()
+
+// Signature is an ECDSA signature, where r and s are represented as big-endian
+// fixed-length byte slices.
+type Signature struct {
+       R, S []byte
+}
+
+// Sign signs a hash (which should be the result of hashing a larger message)
+// using the private key, priv. If the hash is longer than the bit-length of the
+// private key's curve order, the hash will be truncated to that length.
+//
+// The signature is randomized.
+func Sign[P Point[P]](c *Curve[P], priv *PrivateKey, csprng io.Reader, hash []byte) (*Signature, error) {
+       if priv.pub.curve != c.curve {
+               return nil, errors.New("ecdsa: private key does not match curve")
+       }
+
+       // SEC 1, Version 2.0, Section 4.1.3
+
+       k, R, err := randomPoint(c, csprng)
+       if err != nil {
+               return nil, err
+       }
+
+       // kInv = k⁻¹
+       kInv := bigmod.NewNat()
+       inverse(c, kInv, k)
+
+       Rx, err := R.BytesX()
+       if err != nil {
+               return nil, err
+       }
+       r, err := bigmod.NewNat().SetOverflowingBytes(Rx, c.N)
+       if err != nil {
+               return nil, err
+       }
+
+       // The spec wants us to retry here, but the chance of hitting this condition
+       // on a large prime-order group like the NIST curves we support is
+       // cryptographically negligible. If we hit it, something is awfully wrong.
+       if r.IsZero() == 1 {
+               return nil, errors.New("ecdsa: internal error: r is zero")
+       }
+
+       e := bigmod.NewNat()
+       hashToNat(c, e, hash)
+
+       s, err := bigmod.NewNat().SetBytes(priv.d, c.N)
+       if err != nil {
+               return nil, err
+       }
+       s.Mul(r, c.N)
+       s.Add(e, c.N)
+       s.Mul(kInv, c.N)
+
+       // Again, the chance of this happening is cryptographically negligible.
+       if s.IsZero() == 1 {
+               return nil, errors.New("ecdsa: internal error: s is zero")
+       }
+
+       return &Signature{r.Bytes(c.N), s.Bytes(c.N)}, nil
+}
+
+// inverse sets kInv to the inverse of k modulo the order of the curve.
+func inverse[P Point[P]](c *Curve[P], kInv, k *bigmod.Nat) {
+       if c.ordInverse != nil {
+               kBytes, err := c.ordInverse(k.Bytes(c.N))
+               // Some platforms don't implement ordInverse, and always return an error.
+               if err == nil {
+                       _, err := kInv.SetBytes(kBytes, c.N)
+                       if err != nil {
+                               panic("ecdsa: internal error: ordInverse produced an invalid value")
+                       }
+                       return
+               }
+       }
+
+       // Calculate the inverse of s in GF(N) using Fermat's method
+       // (exponentiation modulo P - 2, per Euler's theorem)
+       kInv.Exp(k, c.nMinus2, c.N)
+}
+
+// hashToNat sets e to the left-most bits of hash, according to
+// SEC 1, Section 4.1.3, point 5 and Section 4.1.4, point 3.
+func hashToNat[P Point[P]](c *Curve[P], e *bigmod.Nat, hash []byte) {
+       // ECDSA asks us to take the left-most log2(N) bits of hash, and use them as
+       // an integer modulo N. This is the absolute worst of all worlds: we still
+       // have to reduce, because the result might still overflow N, but to take
+       // the left-most bits for P-521 we have to do a right shift.
+       if size := c.N.Size(); len(hash) >= size {
+               hash = hash[:size]
+               if excess := len(hash)*8 - c.N.BitLen(); excess > 0 {
+                       hash = bytes.Clone(hash)
+                       for i := len(hash) - 1; i >= 0; i-- {
+                               hash[i] >>= excess
+                               if i > 0 {
+                                       hash[i] |= hash[i-1] << (8 - excess)
+                               }
+                       }
+               }
+       }
+       _, err := e.SetOverflowingBytes(hash, c.N)
+       if err != nil {
+               panic("ecdsa: internal error: truncated hash is too long")
+       }
+}
+
+// Verify verifies the signature, sig, of hash (which should be the result of
+// hashing a larger message) using the public key, pub. If the hash is longer
+// than the bit-length of the private key's curve order, the hash will be
+// truncated to that length.
+//
+// The inputs are not considered confidential, and may leak through timing side
+// channels, or if an attacker has control of part of the inputs.
+func Verify[P Point[P]](c *Curve[P], pub *PublicKey, hash []byte, sig *Signature) error {
+       if pub.curve != c.curve {
+               return errors.New("ecdsa: public key does not match curve")
+       }
+
+       Q, err := c.newPoint().SetBytes(pub.q)
+       if err != nil {
+               return err
+       }
+
+       // SEC 1, Version 2.0, Section 4.1.4
+
+       r, err := bigmod.NewNat().SetBytes(sig.R, c.N)
+       if err != nil {
+               return err
+       }
+       if r.IsZero() == 1 {
+               return errors.New("ecdsa: invalid signature: r is zero")
+       }
+       s, err := bigmod.NewNat().SetBytes(sig.S, c.N)
+       if err != nil {
+               return err
+       }
+       if s.IsZero() == 1 {
+               return errors.New("ecdsa: invalid signature: s is zero")
+       }
+
+       e := bigmod.NewNat()
+       hashToNat(c, e, hash)
+
+       // w = s⁻¹
+       w := bigmod.NewNat()
+       inverse(c, w, s)
+
+       // p₁ = [e * s⁻¹]G
+       p1, err := c.newPoint().ScalarBaseMult(e.Mul(w, c.N).Bytes(c.N))
+       if err != nil {
+               return err
+       }
+       // p₂ = [r * s⁻¹]Q
+       p2, err := Q.ScalarMult(Q, w.Mul(r, c.N).Bytes(c.N))
+       if err != nil {
+               return err
+       }
+       // BytesX returns an error for the point at infinity.
+       Rx, err := p1.Add(p1, p2).BytesX()
+       if err != nil {
+               return err
+       }
+
+       v, err := bigmod.NewNat().SetOverflowingBytes(Rx, c.N)
+       if err != nil {
+               return err
+       }
+
+       if v.Equal(r) != 1 {
+               return errors.New("ecdsa: signature did not verify")
+       }
+       return nil
+}
diff --git a/src/crypto/internal/fips/ecdsa/ecdsa_test.go b/src/crypto/internal/fips/ecdsa/ecdsa_test.go
new file mode 100644 (file)
index 0000000..cc53065
--- /dev/null
@@ -0,0 +1,87 @@
+// Copyright 2024 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 ecdsa
+
+import (
+       "bytes"
+       "crypto/internal/fips/bigmod"
+       "crypto/rand"
+       "io"
+       "testing"
+)
+
+func TestRandomPoint(t *testing.T) {
+       t.Run("P-224", func(t *testing.T) { testRandomPoint(t, P224()) })
+       t.Run("P-256", func(t *testing.T) { testRandomPoint(t, P256()) })
+       t.Run("P-384", func(t *testing.T) { testRandomPoint(t, P384()) })
+       t.Run("P-521", func(t *testing.T) { testRandomPoint(t, P521()) })
+}
+
+func testRandomPoint[P Point[P]](t *testing.T, c *Curve[P]) {
+       t.Cleanup(func() { testingOnlyRejectionSamplingLooped = nil })
+       var loopCount int
+       testingOnlyRejectionSamplingLooped = func() { loopCount++ }
+
+       // A sequence of all ones will generate 2^N-1, which should be rejected.
+       // (Unless, for example, we are masking too many bits.)
+       r := io.MultiReader(bytes.NewReader(bytes.Repeat([]byte{0xff}, 100)), rand.Reader)
+       if k, p, err := randomPoint(c, r); err != nil {
+               t.Fatal(err)
+       } else if k.IsZero() == 1 {
+               t.Error("k is zero")
+       } else if p.Bytes()[0] != 4 {
+               t.Error("p is infinity")
+       }
+       if loopCount == 0 {
+               t.Error("overflow was not rejected")
+       }
+       loopCount = 0
+
+       // A sequence of all zeroes will generate zero, which should be rejected.
+       r = io.MultiReader(bytes.NewReader(bytes.Repeat([]byte{0}, 100)), rand.Reader)
+       if k, p, err := randomPoint(c, r); err != nil {
+               t.Fatal(err)
+       } else if k.IsZero() == 1 {
+               t.Error("k is zero")
+       } else if p.Bytes()[0] != 4 {
+               t.Error("p is infinity")
+       }
+       if loopCount == 0 {
+               t.Error("zero was not rejected")
+       }
+       loopCount = 0
+
+       // P-256 has a 2⁻³² chance or randomly hitting a rejection. For P-224 it's
+       // 2⁻¹¹², for P-384 it's 2⁻¹⁹⁴, and for P-521 it's 2⁻²⁶², so if we hit in
+       // tests, something is horribly wrong. (For example, we are masking the
+       // wrong bits.)
+       if c.curve == p256 {
+               return
+       }
+       if k, p, err := randomPoint(c, rand.Reader); err != nil {
+               t.Fatal(err)
+       } else if k.IsZero() == 1 {
+               t.Error("k is zero")
+       } else if p.Bytes()[0] != 4 {
+               t.Error("p is infinity")
+       }
+       if loopCount > 0 {
+               t.Error("unexpected rejection")
+       }
+}
+
+func TestHashToNat(t *testing.T) {
+       t.Run("P-224", func(t *testing.T) { testHashToNat(t, P224()) })
+       t.Run("P-256", func(t *testing.T) { testHashToNat(t, P256()) })
+       t.Run("P-384", func(t *testing.T) { testHashToNat(t, P384()) })
+       t.Run("P-521", func(t *testing.T) { testHashToNat(t, P521()) })
+}
+
+func testHashToNat[P Point[P]](t *testing.T, c *Curve[P]) {
+       for l := 0; l < 600; l++ {
+               h := bytes.Repeat([]byte{0xff}, l)
+               hashToNat(c, bigmod.NewNat(), h)
+       }
+}
index 6babcce406676fbf1b8d476ae39bd33f6a8e574e..17425d46e6ba2fd28174df9ea36772c59b106b93 100644 (file)
@@ -479,6 +479,7 @@ var depsRules = `
        < crypto/internal/fips/nistec/fiat
        < crypto/internal/fips/nistec
        < crypto/internal/fips/ecdh
+       < crypto/internal/fips/ecdsa
        < FIPS;
 
        FIPS < crypto/internal/fips/check/checktest;