derivecert: fix ecdsa code to be deterministic (#3991)

derivecert: fix ecdsa code to be deterministic (#3989)

* derivecert: fix ecdsa code to be deterministic

* lint

Co-authored-by: Caleb Doxsey <cdoxsey@pomerium.com>
This commit is contained in:
backport-actions-token[bot] 2023-02-17 17:08:26 -07:00 committed by GitHub
parent 282418cb50
commit 57d1186d20
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 246 additions and 46 deletions

View file

@ -0,0 +1,156 @@
// Package deterministicecdsa contains the original ecdsa.GenerateKey before it was made non-deterministic.
package deterministicecdsa
// Copyright 2011 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 implements the Elliptic Curve Digital Signature Algorithm, as
// defined in FIPS 186-4 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
// security in case of randomness source failure.
// [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/ecdsa"
"crypto/elliptic"
"io"
"math/big"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
)
var one = new(big.Int).SetInt64(1)
// randFieldElement returns a random element of the order of the given
// curve using the procedure given in FIPS 186-4, Appendix B.5.1.
func randFieldElement(c elliptic.Curve, rand io.Reader) (k *big.Int, err error) {
params := c.Params()
// Note that for P-521 this will actually be 63 bits more than the order, as
// division rounds down, but the extra bit is inconsequential.
b := make([]byte, params.BitSize/8+8) // TODO: use params.N.BitLen()
_, err = io.ReadFull(rand, b)
if err != nil {
return
}
k = new(big.Int).SetBytes(b)
n := new(big.Int).Sub(params.N, one)
k.Mod(k, n)
k.Add(k, one)
return
}
// hashToInt converts a hash value to an integer. There is some disagreement
// about how this is done. [NSA] suggests that this is done in the obvious
// manner, but [SECG] truncates the hash to the bit-length of the curve order
// first. We follow [SECG] because that's what OpenSSL does.
func hashToInt(hash []byte, c elliptic.Curve) *big.Int {
orderBits := c.Params().N.BitLen()
orderBytes := (orderBits + 7) / 8
if len(hash) > orderBytes {
hash = hash[:orderBytes]
}
ret := new(big.Int).SetBytes(hash)
excess := orderBytes*8 - orderBits
if excess > 0 {
ret.Rsh(ret, uint(excess))
}
return ret
}
// GenerateKey generates a public and private key pair.
func GenerateKey(c elliptic.Curve, rand io.Reader) (*ecdsa.PrivateKey, error) {
k, err := randFieldElement(c, rand)
if err != nil {
return nil, err
}
priv := new(ecdsa.PrivateKey)
priv.PublicKey.Curve = c
priv.D = k
priv.PublicKey.X, priv.PublicKey.Y = c.ScalarBaseMult(k.Bytes())
return priv, nil
}
type deterministicPrivateKey struct {
*ecdsa.PrivateKey
}
// WrapPrivateKey wraps a private key so that the Sign method is deterministic
func WrapPrivateKey(privateKey *ecdsa.PrivateKey) crypto.PrivateKey {
return deterministicPrivateKey{PrivateKey: privateKey}
}
// Sign signs digest with priv, reading randomness from rand. The opts argument
// is not currently used but, in keeping with the crypto.Signer interface,
// should be the hash function used to digest the message.
//
// This method implements crypto.Signer, which is an interface to support keys
// where the private part is kept in, for example, a hardware module. Common
// uses can use the SignASN1 function in this package directly.
func (priv deterministicPrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
r, s, err := Sign(rand, priv.PrivateKey, digest)
if err != nil {
return nil, err
}
var b cryptobyte.Builder
b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
b.AddASN1BigInt(r)
b.AddASN1BigInt(s)
})
return b.Bytes()
}
// Sign signs an arbitrary length hash (which should be the result of hashing a
// larger message) using the private key, priv. It returns the signature as a
// pair of integers. The security of the private key depends on the entropy of
// rand.
func Sign(rand io.Reader, priv *ecdsa.PrivateKey, hash []byte) (r, s *big.Int, err error) {
// See [NSA] 3.4.1
c := priv.PublicKey.Curve
N := c.Params().N
var k, kInv *big.Int //nolint
for {
for {
k, err = randFieldElement(c, rand)
if err != nil {
r = nil
return
}
kInv = new(big.Int).ModInverse(k, N)
r, _ = priv.Curve.ScalarBaseMult(k.Bytes())
r.Mod(r, N)
if r.Sign() != 0 {
break
}
}
e := hashToInt(hash, c)
s = new(big.Int).Mul(priv.D, r)
s.Add(s, e)
s.Mul(s, kInv)
s.Mod(s, N)
if s.Sign() != 0 {
break
}
}
return //nolint
}

View file

@ -2,21 +2,19 @@ package derivecert
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"io"
"math/big"
"time"
"golang.org/x/crypto/hkdf"
"github.com/pomerium/pomerium/internal/deterministicecdsa"
)
// CA is certificate authority
type CA struct {
psk []byte
// key is signing key
key *ecdsa.PrivateKey
// cert is a CA certificate
@ -45,9 +43,9 @@ var (
// and provides a better alternative to plaintext communication,
// but is not a replacement for proper mTLS.
func NewCA(psk []byte) (*CA, error) {
key, err := ecdsa.GenerateKey(elliptic.P256(), pskRandReader(psk))
key, err := deriveKey(newReader(readerTypeCAPrivateKey, psk))
if err != nil {
return nil, fmt.Errorf("generating key: %w", err)
return nil, fmt.Errorf("derive key: %w", err)
}
cert, err := caCertTemplate(psk)
@ -55,7 +53,11 @@ func NewCA(psk []byte) (*CA, error) {
return nil, err
}
der, err := x509.CreateCertificate(pskRandReader(psk), cert, cert, &key.PublicKey, key)
der, err := x509.CreateCertificate(
newReader(readerTypeCACertificate, psk),
cert, cert,
key.Public(), deterministicecdsa.WrapPrivateKey(key),
)
if err != nil {
return nil, fmt.Errorf("create cert: %w", err)
}
@ -64,7 +66,7 @@ func NewCA(psk []byte) (*CA, error) {
return nil, fmt.Errorf("parse cert: %w", err)
}
ca := &CA{key, cert}
ca := &CA{psk, key, cert}
return ca, nil
}
@ -82,17 +84,21 @@ func CAFromPEM(p PEM) (*CA, string, error) {
// NewServerCert generates certificate for the given domain name(s)
func (ca *CA) NewServerCert(domains []string) (*PEM, error) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
key, err := deriveKey(newReader(readerTypeServerPrivateKey, ca.psk, domains...))
if err != nil {
return nil, fmt.Errorf("generate key: %w", err)
return nil, fmt.Errorf("derive key: %w", err)
}
tmpl, err := serverCertTemplate(domains)
tmpl, err := serverCertTemplate(ca.psk, domains)
if err != nil {
return nil, fmt.Errorf("cert template: %w", err)
}
cert, err := x509.CreateCertificate(rand.Reader, tmpl, ca.cert, key.Public(), ca.key)
cert, err := x509.CreateCertificate(
newReader(readerTypeServerCertificate, ca.psk, domains...),
tmpl, ca.cert,
key.Public(), deterministicecdsa.WrapPrivateKey(ca.key),
)
if err != nil {
return nil, fmt.Errorf("create cert: %w", err)
}
@ -105,12 +111,8 @@ func (ca *CA) PEM() (*PEM, error) {
return ToPEM(ca.key, ca.cert.Raw)
}
func pskRandReader(psk []byte) io.Reader {
return hkdf.New(sha256.New, psk, nil, nil)
}
func caCertTemplate(psk []byte) (*x509.Certificate, error) {
serial, err := newSerial()
serial, err := newSerial(psk)
if err != nil {
return nil, err
}
@ -127,18 +129,18 @@ func caCertTemplate(psk []byte) (*x509.Certificate, error) {
}, nil
}
func serverCertTemplate(domains []string) (*x509.Certificate, error) {
serial, err := newSerial()
func serverCertTemplate(psk []byte, domains []string) (*x509.Certificate, error) {
serial, err := newSerial(psk, domains...)
if err != nil {
return nil, err
}
return &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{Organization: []string{"Pomerium"}, CommonName: "Pomerium PSK domain cert"},
Subject: pkix.Name{Organization: []string{"Pomerium"}},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
DNSNames: domains,
}, nil
@ -149,9 +151,9 @@ func (ca *CA) Key() *ecdsa.PrivateKey {
return ca.key
}
func newSerial() (*big.Int, error) {
func newSerial(psk []byte, domains ...string) (*big.Int, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
serialNumber, err := rand.Int(newReader(readerTypeSerialNumber, psk, domains...), serialNumberLimit)
if err != nil {
return nil, fmt.Errorf("failed to generate serial number: %w", err)
}

View file

@ -19,6 +19,7 @@ func TestCA(t *testing.T) {
_, err := rand.Read(psk)
require.NoError(t, err)
for i := 0; i < 100; i++ {
ca1, err := derivecert.NewCA(psk)
require.NoError(t, err)
ca2, err := derivecert.NewCA(psk)
@ -48,4 +49,5 @@ func TestCA(t *testing.T) {
_, err = serverCert.Verify(opts)
require.NoError(t, err)
}
}

40
pkg/derivecert/notrand.go Normal file
View file

@ -0,0 +1,40 @@
package derivecert
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256"
"io"
"golang.org/x/crypto/hkdf"
"github.com/pomerium/pomerium/internal/deterministicecdsa"
)
type readerType byte
const (
readerTypeCAPrivateKey readerType = iota
readerTypeCACertificate
readerTypeServerPrivateKey
readerTypeServerCertificate
readerTypeSerialNumber
)
func newReader(readerType readerType, psk []byte, domains ...string) io.Reader {
var buf bytes.Buffer
buf.WriteByte(byte(readerType))
buf.Write(psk)
buf.WriteByte(0)
for _, domain := range domains {
buf.WriteString(domain)
buf.WriteByte(0)
}
return hkdf.New(sha256.New, buf.Bytes(), nil, nil)
}
func deriveKey(r io.Reader) (*ecdsa.PrivateKey, error) {
return deterministicecdsa.GenerateKey(elliptic.P256(), r)
}

View file

@ -18,7 +18,7 @@ type PEM struct {
func ToPEM(key *ecdsa.PrivateKey, certDer []byte) (*PEM, error) {
b, err := x509.MarshalECPrivateKey(key)
if err != nil {
return nil, fmt.Errorf("unable to marshal ECDSA private key: %w", err)
return nil, fmt.Errorf("unable to marshal ecdsa private key: %w", err)
}
return &PEM{
Key: pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b}),