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:
parent
b22c585c2a
commit
45c6cc3020
@ -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
|
||||
|
69
src/crypto/internal/fips/ecdsa/cast.go
Normal file
69
src/crypto/internal/fips/ecdsa/cast.go
Normal 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
|
||||
})
|
||||
})
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user