From: Filippo Valsorda Date: Sat, 16 Nov 2024 15:38:07 +0000 (+0100) Subject: crypto/ecdsa: move implementation to crypto/internal/fips/ecdsa X-Git-Tag: go1.24rc1~221 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=03f075b56e2c8214268ce4efc9e67da7474af72d;p=gostls13.git crypto/ecdsa: move implementation to crypto/internal/fips/ecdsa For #69536 Change-Id: I8794d75c11cdadd91e420541b26af35e62006af4 Reviewed-on: https://go-review.googlesource.com/c/go/+/628677 Auto-Submit: Filippo Valsorda Reviewed-by: Dmitri Shuralyov Reviewed-by: Russ Cox LUCI-TryBot-Result: Go LUCI --- diff --git a/src/crypto/ecdsa/ecdsa.go b/src/crypto/ecdsa/ecdsa.go index 0973f82098..534512bcba 100644 --- a/src/crypto/ecdsa/ecdsa.go +++ b/src/crypto/ecdsa/ecdsa.go @@ -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 { + r, s, 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() -} diff --git a/src/crypto/ecdsa/ecdsa_test.go b/src/crypto/ecdsa/ecdsa_test.go index 25ccc52dad..5788fee3a0 100644 --- a/src/crypto/ecdsa/ecdsa_test.go +++ b/src/crypto/ecdsa/ecdsa_test.go @@ -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 index 0000000000..5b4cf8a523 --- /dev/null +++ b/src/crypto/internal/fips/ecdsa/ecdsa.go @@ -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 index 0000000000..cc53065b48 --- /dev/null +++ b/src/crypto/internal/fips/ecdsa/ecdsa_test.go @@ -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) + } +} diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 6babcce406..17425d46e6 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -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;