Description
According to the Debian and the Google internal code search, crypto/elliptic is used almost exclusively as part of ECDSA (via crypto/ecdsa
) and ECDH. It is however a very low-level and unsafe API for ECDH.
As part of an effort to move math/big
outside the security perimeter, I have been moving the NIST curve implementations to a safe API in the nistec
package (#52182). The nistec
API is safe but still lower-level than necessary.
ECDH is used in TLS, SSH, JOSE, OpenPGP, PIV, and HPKE, as well as a component of other various ECIES schemes. There are a myriad of standards (ISO, NIST, ANSI, SECG, IETF) but thankfully they all work the same for NIST P curves at the ECDH level.
I'm proposing adding a new crypto/ecdh
package that exposes a safe, []byte
-based API for ECDH.
Between this package and crypto/ecdsa
, there should be no need for direct uses of crypto/elliptic
, and the big.Int
-based methods of elliptic.Curve
(ScalarMult
, ScalarBaseMult
, Add
, Double
, IsOnCurve
) can be deprecated.
Below is the proposed API. Here are the motivating design goals of the API:
- Most direct uses of crypto/elliptic can be replaced with it.
- No support for custom curves.
- Ideally, X25519 can be supported alongside NIST curves.
- It can be implemented entirely in constant time.
- Invalid states can't be represented.
- For example, it's not possible to provide an invalid point to the scalar multiplication.
- Errors are returned for every invalid input.
- Using one curve does not make the other curve implementations reachable.
- This helps both with binary size and with govulncheck accuracy.
- PublicKey and PrivateKey are compatible with analogous types in other packages.
- This is why PrivateKey has a
Public() crypto.PublicKey
method.
- This is why PrivateKey has a
- It's possible to add any additional methods we realized might be necessary later.
- This is why the main interface has a private method, so it can't be implemented by external types and can be extended while retaining backwards compatibility.
- In addition to the standard ECDH flow, it's possible to validate a public key, and to convert a private key to a public key.
- With collaboration from the compiler it's possible to use the API with zero allocations.
- For the proposed API, this requires devirtualization, then inlining, then escape analysis.
- See cmd/compile: inlining after devirtualization #52193
/cc @golang/security @golang/proposal-review
package ecdh
type Curve interface {
// ECDH performs a ECDH exchange and returns the shared secret.
//
// For NIST curves, this performs ECDH as specified in SEC 1, Version 2.0,
// Section 3.3.1, and returns the x-coordinate encoded according to SEC 1,
// Version 2.0, Section 2.3.5. In particular, if the result is the point at
// infinity, ECDH returns an error. (Note that for NIST curves, that's only
// possible if the private key is the all-zero value.)
//
// For X25519, this performs ECDH as specified in RFC 7748, Section 6.1. If
// the result is the all-zero value, ECDH returns an error.
ECDH(local *PrivateKey, remote *PublicKey) ([]byte, error)
// GenerateKey generates a new PrivateKey from rand.
GenerateKey(rand io.Reader) (*PrivateKey, error)
// NewPrivateKey checks that key is valid and returns a PrivateKey.
//
// For NIST curves, this follows SEC 1, Version 2.0, Section 2.3.6, which
// amounts to decoding the bytes as a fixed length big endian integer and
// checking that the result is lower than the order of the curve. The zero
// private key is also rejected, as the encoding of the corresponding public
// key would be irregular.
//
// For X25519, this only checks the scalar length. Adversarially selected
// private keys can cause ECDH to return an error.
NewPrivateKey(key []byte) (*PrivateKey, error)
// NewPublicKey checks that key is valid and returns a PublicKey.
//
// For NIST curves, this decodes an uncompressed point according to SEC 1,
// Version 2.0, Section 2.3.4. Compressed encodings and the point at
// infinity are rejected.
//
// For X25519, this only checks the u-coordinate length. Adversarially
// selected public keys can cause ECDH to return an error.
NewPublicKey(key []byte) (*PublicKey, error)
// Has unexported methods.
}
func P256() Curve
func P384() Curve
func P521() Curve
func X25519() Curve
type PrivateKey struct {
// Has unexported fields.
}
func (k *PrivateKey) Bytes() []byte
func (k *PrivateKey) Curve() Curve
func (k *PrivateKey) Equal(x crypto.PrivateKey) bool
func (k *PrivateKey) Public() crypto.PublicKey
func (k *PrivateKey) PublicKey() *PublicKey
type PublicKey struct {
// Has unexported fields.
}
func (k *PublicKey) Bytes() []byte
func (k *PublicKey) Curve() Curve
func (k *PublicKey) Equal(x crypto.PublicKey) bool