1
0
mirror of https://github.com/golang/go synced 2024-11-26 10:48:22 -07:00

crypto/internal/fips/ecdsa: add CAST, PCT, DRBG, and FIPS 186-5 references

The previous CL focused on moving the implementation as-is, while this
makes it FIPS-compliant.

For #69536

Change-Id: I75fa56c7e13ba20246bacf9fda4599c9f25a1c63
Reviewed-on: https://go-review.googlesource.com/c/go/+/628678
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Russ Cox <rsc@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
This commit is contained in:
Filippo Valsorda 2024-11-16 17:44:17 +01:00 committed by Gopher Robot
parent b22c585c2a
commit 45c6cc3020
4 changed files with 150 additions and 43 deletions

View File

@ -3,7 +3,7 @@
// 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
@ -14,15 +14,6 @@
// [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"
@ -234,6 +225,9 @@ func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) {
}
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

View File

@ -0,0 +1,69 @@
// 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
})
})

View File

@ -6,7 +6,9 @@ package ecdsa
import (
"bytes"
"crypto/internal/fips"
"crypto/internal/fips/bigmod"
"crypto/internal/fips/drbg"
"crypto/internal/fips/nistec"
"errors"
"io"
@ -154,7 +156,8 @@ var p521Order = []byte{0x01, 0xff,
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
}
@ -162,49 +165,85 @@ func NewPrivateKey[P Point[P]](c *Curve[P], D, Q []byte) (*PrivateKey, error) {
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
@ -220,21 +259,20 @@ func randomPoint[P Point[P]](c *Curve[P], rand io.Reader) (k *bigmod.Nat, p P, e
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
@ -251,16 +289,18 @@ type Signature struct {
// 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 {
@ -326,7 +366,7 @@ func inverse[P Point[P]](c *Curve[P], kInv, k *bigmod.Nat) {
}
// 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
@ -361,17 +401,19 @@ func Verify[P Point[P]](c *Curve[P], pub *PublicKey, hash []byte, sig *Signature
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

View File

@ -18,6 +18,7 @@ import (
_ "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"
@ -71,6 +72,7 @@ func findAllCASTs(t *testing.T) map[string]struct{} {
func TestConditionals(t *testing.T) {
mlkem.GenerateKey768()
ecdh.GenerateKeyP256(rand.Reader)
ecdsa.GenerateKey(ecdsa.P256(), rand.Reader)
t.Log("completed successfully")
}