// license that can be found in the LICENSE file.
// Package ecdsa implements the Elliptic Curve Digital Signature Algorithm, as
-// defined in FIPS 186-4 and SEC 1, Version 2.0.
+// defined in FIPS 186-5 and SEC 1, Version 2.0.
//
// Signatures generated by this package are not deterministic, but entropy is
// mixed with the private key and the message, achieving the same level of
// [elliptic.P256], [elliptic.P384], or [elliptic.P521] is used.
package ecdsa
-// [FIPS 186-4] references ANSI X9.62-2005 for the bulk of the ECDSA algorithm.
-// That standard is not freely available, which is a problem in an open source
-// implementation, because not only the implementer, but also any maintainer,
-// contributor, reviewer, auditor, and learner needs access to it. Instead, this
-// package references and follows the equivalent [SEC 1, Version 2.0].
-//
-// [FIPS 186-4]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
-// [SEC 1, Version 2.0]: https://www.secg.org/sec1-v2.pdf
-
import (
"crypto"
"crypto/aes"
}
func signFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey, csprng io.Reader, hash []byte) ([]byte, error) {
+ // privateKeyToFIPS is very slow in FIPS mode because it performs a
+ // Sign+Verify cycle per FIPS 140-3 IG 10.3.A. We should find a way to cache
+ // it or attach it to the PrivateKey.
k, err := privateKeyToFIPS(c, priv)
if err != nil {
return nil, err
--- /dev/null
+// 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"
+ "errors"
+ "sync"
+)
+
+var fipsSelfTest = sync.OnceFunc(func() {
+ fips.CAST("ECDSA P-256 SHA2-256", func() error {
+ // https://www.rfc-editor.org/rfc/rfc9500.html#section-2.3
+ k := &PrivateKey{
+ pub: PublicKey{
+ curve: p256,
+ q: []byte{
+ 0x04,
+ 0x42, 0x25, 0x48, 0xF8, 0x8F, 0xB7, 0x82, 0xFF,
+ 0xB5, 0xEC, 0xA3, 0x74, 0x44, 0x52, 0xC7, 0x2A,
+ 0x1E, 0x55, 0x8F, 0xBD, 0x6F, 0x73, 0xBE, 0x5E,
+ 0x48, 0xE9, 0x32, 0x32, 0xCC, 0x45, 0xC5, 0xB1,
+ 0x6C, 0x4C, 0xD1, 0x0C, 0x4C, 0xB8, 0xD5, 0xB8,
+ 0xA1, 0x71, 0x39, 0xE9, 0x48, 0x82, 0xC8, 0x99,
+ 0x25, 0x72, 0x99, 0x34, 0x25, 0xF4, 0x14, 0x19,
+ 0xAB, 0x7E, 0x90, 0xA4, 0x2A, 0x49, 0x42, 0x72},
+ },
+ d: []byte{
+ 0xE6, 0xCB, 0x5B, 0xDD, 0x80, 0xAA, 0x45, 0xAE,
+ 0x9C, 0x95, 0xE8, 0xC1, 0x54, 0x76, 0x67, 0x9F,
+ 0xFE, 0xC9, 0x53, 0xC1, 0x68, 0x51, 0xE7, 0x11,
+ 0xE7, 0x43, 0x93, 0x95, 0x89, 0xC6, 0x4F, 0xC1,
+ },
+ }
+ hash := []byte{
+ 0x32, 0xb3, 0xda, 0x19, 0xf9, 0x44, 0xb0, 0x48,
+ 0x66, 0xd9, 0x31, 0xa6, 0x6c, 0x30, 0xb9, 0x4a,
+ 0xe7, 0x28, 0xcc, 0xe7, 0x00, 0xe4, 0xb0, 0xa5,
+ 0xb4, 0xfe, 0xae, 0x8f, 0x43, 0x8f, 0xde, 0xc2,
+ }
+ want := &Signature{
+ R: []byte{
+ 0x6b, 0x17, 0xd1, 0xf2, 0xe1, 0x2c, 0x42, 0x47,
+ 0xf8, 0xbc, 0xe6, 0xe5, 0x63, 0xa4, 0x40, 0xf2,
+ 0x77, 0x03, 0x7d, 0x81, 0x2d, 0xeb, 0x33, 0xa0,
+ 0xf4, 0xa1, 0x39, 0x45, 0xd8, 0x98, 0xc2, 0x96,
+ }, S: []byte{
+ 0x2d, 0x4f, 0x31, 0xc9, 0x07, 0x35, 0x74, 0x2b,
+ 0x82, 0x04, 0x19, 0xa0, 0x97, 0xb1, 0x4a, 0x75,
+ 0x99, 0xf7, 0x8a, 0x54, 0xf8, 0x3b, 0xe9, 0x89,
+ 0x50, 0x26, 0x35, 0x32, 0x52, 0x0f, 0x73, 0xfc,
+ },
+ }
+ got, err := sign(P256(), k, testingOnlyFixedRandomPoint, hash)
+ if err != nil {
+ return err
+ }
+ if err := verify(P256(), &k.pub, hash, got); err != nil {
+ return err
+ }
+ if !bytes.Equal(got.R, want.R) || !bytes.Equal(got.S, want.S) {
+ return errors.New("unexpected result")
+ }
+ return nil
+ })
+})
import (
"bytes"
+ "crypto/internal/fips"
"crypto/internal/fips/bigmod"
+ "crypto/internal/fips/drbg"
"crypto/internal/fips/nistec"
"errors"
"io"
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)
+ fips.RecordApproved()
+ pub, err := NewPublicKey(c, Q)
if err != nil {
return nil, err
}
if err != nil {
return nil, err
}
- return &PrivateKey{
- pub: PublicKey{
- curve: c.curve,
- q: Q,
- },
- d: d.Bytes(c.N),
- }, nil
+ priv := &PrivateKey{pub: *pub, d: d.Bytes(c.N)}
+ fips.CAST("ECDSA PCT", func() error { return fipsPCT(c, priv) })
+ return priv, nil
}
func NewPublicKey[P Point[P]](c *Curve[P], Q []byte) (*PublicKey, error) {
+ // SetBytes checks that Q is a valid point on the curve, and that its
+ // coordinates are reduced modulo p, fulfilling the requirements of SP
+ // 800-89, Section 5.3.2.
_, err := c.newPoint().SetBytes(Q)
if err != nil {
return nil, err
}
- return &PublicKey{
- curve: c.curve,
- q: Q,
- }, nil
+ return &PublicKey{curve: c.curve, q: Q}, nil
}
// GenerateKey generates a new ECDSA private key pair for the specified curve.
+//
+// In FIPS mode, rand is ignored.
func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) {
+ fips.RecordApproved()
k, Q, err := randomPoint(c, rand)
if err != nil {
return nil, err
}
- return &PrivateKey{
+ priv := &PrivateKey{
pub: PublicKey{
curve: c.curve,
q: Q.Bytes(),
},
d: k.Bytes(c.N),
- }, nil
+ }
+ fips.CAST("ECDSA PCT", func() error { return fipsPCT(c, priv) })
+ return priv, 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 fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) error {
+ hash := []byte{
+ 0x32, 0xb3, 0xda, 0x19, 0xf9, 0x44, 0xb0, 0x48,
+ 0x66, 0xd9, 0x31, 0xa6, 0x6c, 0x30, 0xb9, 0x4a,
+ 0xe7, 0x28, 0xcc, 0xe7, 0x00, 0xe4, 0xb0, 0xa5,
+ 0xb4, 0xfe, 0xae, 0x8f, 0x43, 0x8f, 0xde, 0xc2,
+ }
+ sig, err := Sign(c, k, nil, hash)
+ if err != nil {
+ return err
+ }
+ return Verify(c, &k.pub, hash, sig)
+}
+
+type testingOnlyFixedRandomPointReader struct{}
+
+func (testingOnlyFixedRandomPointReader) Read(p []byte) (int, error) { panic("not implemented") }
+
+// testingOnlyFixedRandomPoint is a signal to Sign that it should use a fixed
+// random point to enable the CAST. This is extremely dangerous because if it
+// were used with a production private key it would leak the private key.
+// We use this rather than a global variable to avoid accidentally using it in
+// production code. Still wish we didn't have to do this.
+var testingOnlyFixedRandomPoint = testingOnlyFixedRandomPointReader{}
+
+// randomPoint returns a random scalar and the corresponding point using a
+// procedure equivalent to FIPS 186-5, Appendix A.2.2 (ECDSA Key Pair Generation
+// by Rejection Sampling) and to Appendix A.2.2 (Per-Message Secret Number
+// Generation of Private Keys by Rejection Sampling) followed by Step 5 of
+// Section 6.4.1.
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
+ if fips.Enabled {
+ if rand == testingOnlyFixedRandomPoint {
+ b[len(b)-1] = 1
+ } else {
+ drbg.Read(b)
+ }
+ } else {
+ if _, err := io.ReadFull(rand, b); err != nil {
+ return nil, nil, err
+ }
}
// Mask off any excess bits to increase the chance of hitting a value in
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
+ // FIPS 186-5, Appendix A.4.2 makes us check x <= N - 2 and then return
+ // x + 1. Note that it follows that 0 < x + 1 < N. Instead, SetBytes
+ // checks that k < N, and we explicitly check 0 != k. Since k can't be
+ // negative, this is strictly equivalent. None of this matters anyway
+ // because the chance of selecting zero is cryptographically negligible.
+ if k, err := bigmod.NewNat().SetBytes(b, c.N); err == nil && k.IsZero() == 0 {
+ p, err := c.newPoint().ScalarBaseMult(k.Bytes(c.N))
+ return k, p, err
}
if testingOnlyRejectionSamplingLooped != nil {
testingOnlyRejectionSamplingLooped()
}
}
-
- p, err = c.newPoint().ScalarBaseMult(k.Bytes(c.N))
- return
}
// testingOnlyRejectionSamplingLooped is called when rejection sampling in
// 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.
+// The signature is randomized. If FIPS mode is enabled, csprng is ignored.
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")
}
+ fips.RecordApproved()
+ fipsSelfTest()
return sign(c, priv, csprng, hash)
}
func signGeneric[P Point[P]](c *Curve[P], priv *PrivateKey, csprng io.Reader, hash []byte) (*Signature, error) {
- // SEC 1, Version 2.0, Section 4.1.3
+ // FIPS 186-5, Section 6.4.1
k, R, err := randomPoint(c, csprng)
if err != nil {
}
// 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.
+// FIPS 186-5, Section 6.4.1, point 2 and Section 6.4.2, 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
if pub.curve != c.curve {
return errors.New("ecdsa: public key does not match curve")
}
+ fips.RecordApproved()
+ fipsSelfTest()
return verify(c, pub, hash, sig)
}
func verifyGeneric[P Point[P]](c *Curve[P], pub *PublicKey, hash []byte, sig *Signature) error {
+ // FIPS 186-5, Section 6.4.2
+
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
_ "crypto/internal/fips/aes/gcm"
_ "crypto/internal/fips/drbg"
"crypto/internal/fips/ecdh"
+ "crypto/internal/fips/ecdsa"
_ "crypto/internal/fips/hkdf"
_ "crypto/internal/fips/hmac"
"crypto/internal/fips/mlkem"
func TestConditionals(t *testing.T) {
mlkem.GenerateKey768()
ecdh.GenerateKeyP256(rand.Reader)
+ ecdsa.GenerateKey(ecdsa.P256(), rand.Reader)
t.Log("completed successfully")
}