Skip to content

Commit

Permalink
runnner: Switch to Go's crypto/ecdh module
Browse files Browse the repository at this point in the history
This lets us fold X25519 into the ECDH bits, and drop
x/crypto/curve25519, but then means we need to carry a copy of the
crypto/elliptic version for P-224 because crypto/ecdh doesn't support
it.

Change-Id: Ie053679f26462c68c1d553de97e06afdb77c7eed
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/76208
Reviewed-by: Bob Beck <[email protected]>
Auto-Submit: David Benjamin <[email protected]>
Commit-Queue: David Benjamin <[email protected]>
  • Loading branch information
davidben authored and Boringssl LUCI CQ committed Feb 13, 2025
1 parent 9aa2f99 commit 2be18f5
Showing 1 changed file with 81 additions and 73 deletions.
154 changes: 81 additions & 73 deletions ssl/test/runner/key_agreement.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ package runner

import (
"crypto"
"crypto/ecdh"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/mlkem"
"crypto/rsa"
"crypto/subtle"
"crypto/x509"
"errors"
"fmt"
Expand All @@ -20,7 +20,6 @@ import (
"slices"

"boringssl.googlesource.com/boringssl.git/ssl/test/runner/kyber"
"golang.org/x/crypto/curve25519"
)

type keyType int
Expand Down Expand Up @@ -249,48 +248,53 @@ type kemImplementation interface {
decap(config *Config, ciphertext []byte) (secret []byte, err error)
}

// ecdhKEM implements kemImplementation with an elliptic.Curve.
//
// TODO(davidben): Move this to Go's crypto/ecdh.
type ecdhKEM struct {
curve elliptic.Curve
func applyBugsToECDHPublicKey(config *Config, publicKey []byte) []byte {
if config.Bugs.SendCompressedCoordinates {
l := (len(publicKey) - 1) / 2
tmp := make([]byte, 1+l)
// Extract the low-order bit of the y-coordinate.
tmp[0] = byte(2 | (publicKey[len(publicKey)-1] & 1))
copy(tmp[1:], publicKey[1:1+l])
publicKey = tmp
}
if config.Bugs.ECDHPointNotOnCurve {
// Flip a bit, so the point is no longer on the curve. This is
// guaranteed to be off the curve because we preserve x. That
// means the only other valid y is y' = p - y, but we've kept
// y's parity, so we cannot have accidentally reached y'.
publicKey[len(publicKey)-1] ^= 0x80
}
return publicKey
}

// p224KEM implements kemImplementation with P-224. Go's crypto/ecdh does not
// support P-224.
type p224KEM struct {
privateKey []byte
}

func (e *ecdhKEM) encapsulationKeySize() int {
fieldBytes := (e.curve.Params().BitSize + 7) / 8
func (e *p224KEM) encapsulationKeySize() int {
fieldBytes := (elliptic.P224().Params().Params().BitSize + 7) / 8
return 1 + 2*fieldBytes
}

func (e *ecdhKEM) ciphertextSize() int {
func (e *p224KEM) ciphertextSize() int {
return e.encapsulationKeySize()
}

func (e *ecdhKEM) generate(config *Config) (publicKey []byte, err error) {
func (e *p224KEM) generate(config *Config) (publicKey []byte, err error) {
p224 := elliptic.P224().Params()
var x, y *big.Int
e.privateKey, x, y, err = elliptic.GenerateKey(e.curve, config.rand())
e.privateKey, x, y, err = elliptic.GenerateKey(p224, config.rand())
if err != nil {
return nil, err
}
ret := elliptic.Marshal(e.curve, x, y)
if config.Bugs.SendCompressedCoordinates {
l := (len(ret) - 1) / 2
tmp := make([]byte, 1+l)
tmp[0] = byte(2 | y.Bit(0))
copy(tmp[1:], ret[1:1+l])
ret = tmp
}
if config.Bugs.ECDHPointNotOnCurve {
// Flip a bit, so the point is no longer on the curve. This is
// guaranteed to be off the curve because we preserve x. That
// means the only other valid y is y' = p - y, but we've kept
// y's parity, so we cannot have accidentally reached y'.
ret[len(ret)-1] ^= 0x80
}
ret := elliptic.Marshal(p224, x, y)
ret = applyBugsToECDHPublicKey(config, ret)
return ret, nil
}

func (e *ecdhKEM) encap(config *Config, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
func (e *p224KEM) encap(config *Config, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
ciphertext, err = e.generate(config)
if err != nil {
return nil, nil, err
Expand All @@ -302,50 +306,64 @@ func (e *ecdhKEM) encap(config *Config, peerKey []byte) (ciphertext []byte, secr
return
}

func (e *ecdhKEM) decap(config *Config, ciphertext []byte) (secret []byte, err error) {
x, y := elliptic.Unmarshal(e.curve, ciphertext)
func (e *p224KEM) decap(config *Config, ciphertext []byte) (secret []byte, err error) {
p224 := elliptic.P224().Params()
x, y := elliptic.Unmarshal(p224, ciphertext)
if x == nil {
return nil, errors.New("tls: invalid peer key")
}
x, _ = e.curve.ScalarMult(x, y, e.privateKey)
secret = make([]byte, (e.curve.Params().BitSize+7)>>3)
x, _ = p224.ScalarMult(x, y, e.privateKey)
secret = make([]byte, (p224.Params().BitSize+7)>>3)
xBytes := x.Bytes()
copy(secret[len(secret)-len(xBytes):], xBytes)
return secret, nil
}

// x25519KEM implements kemImplementation with X25519.
type x25519KEM struct {
privateKey [32]byte
// ecdhKEM implements kemImplementation with crypto/ecdh.
type ecdhKEM struct {
curve ecdh.Curve
privateKey *ecdh.PrivateKey
}

func (e *x25519KEM) encapsulationKeySize() int {
return curve25519.PointSize
func (e *ecdhKEM) encapsulationKeySize() int {
switch e.curve {
case ecdh.P256():
return 1 + 2*32
case ecdh.P384():
return 1 + 2*48
case ecdh.P521():
return 1 + 2*66
case ecdh.X25519():
return 32
}
panic(fmt.Sprintf("unknown curve %q", e.curve))
}

func (e *x25519KEM) ciphertextSize() int {
return curve25519.PointSize
func (e *ecdhKEM) ciphertextSize() int {
return e.encapsulationKeySize()
}

func (e *x25519KEM) generate(config *Config) (publicKey []byte, err error) {
if config.Bugs.LowOrderX25519Point {
func (e *ecdhKEM) generate(config *Config) (publicKey []byte, err error) {
if e.curve == ecdh.X25519() && config.Bugs.LowOrderX25519Point {
publicKey = []byte{0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3, 0xfa, 0xf1, 0x9f, 0xc4, 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, 0x00}
return
}

_, err = io.ReadFull(config.rand(), e.privateKey[:])
e.privateKey, err = e.curve.GenerateKey(config.rand())
if err != nil {
return
return nil, err
}
var out [32]byte
curve25519.ScalarBaseMult(&out, &e.privateKey)
if config.Bugs.SetX25519HighBit {
out[31] |= 0x80
ret := e.privateKey.PublicKey().Bytes()
if e.curve == ecdh.X25519() {
if config.Bugs.SetX25519HighBit {
ret[31] |= 0x80
}
} else {
ret = applyBugsToECDHPublicKey(config, ret)
}
return out[:], nil
return ret, nil
}

func (e *x25519KEM) encap(config *Config, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
func (e *ecdhKEM) encap(config *Config, peerKey []byte) (ciphertext []byte, secret []byte, err error) {
ciphertext, err = e.generate(config)
if err != nil {
return nil, nil, err
Expand All @@ -357,26 +375,16 @@ func (e *x25519KEM) encap(config *Config, peerKey []byte) (ciphertext []byte, se
return
}

func (e *x25519KEM) decap(config *Config, ciphertext []byte) (secret []byte, err error) {
if len(ciphertext) != 32 {
return nil, errors.New("tls: invalid peer key")
}

if config.Bugs.LowOrderX25519Point {
func (e *ecdhKEM) decap(config *Config, ciphertext []byte) (secret []byte, err error) {
if e.curve == ecdh.X25519() && config.Bugs.LowOrderX25519Point {
secret = make([]byte, 32)
return
}

var out [32]byte
curve25519.ScalarMult(&out, &e.privateKey, (*[32]byte)(ciphertext))

// Per RFC 7748, reject the all-zero value in constant time.
var zeros [32]byte
if subtle.ConstantTimeCompare(zeros[:], out[:]) == 1 {
return nil, errors.New("tls: X25519 value with wrong order")
peerKey, err := e.curve.NewPublicKey(ciphertext)
if err != nil {
return nil, fmt.Errorf("tls: invalid peer ECDH key: %s", err)
}

return out[:], nil
return e.privateKey.ECDH(peerKey)
}

// kyberKEM implements Kyber-768
Expand Down Expand Up @@ -567,21 +575,21 @@ func kemForCurveID(id CurveID, config *Config) (kemImplementation, bool) {
var kem kemImplementation
switch id {
case CurveP224:
kem = &ecdhKEM{curve: elliptic.P224()}
kem = &p224KEM{}
case CurveP256:
kem = &ecdhKEM{curve: elliptic.P256()}
kem = &ecdhKEM{curve: ecdh.P256()}
case CurveP384:
kem = &ecdhKEM{curve: elliptic.P384()}
kem = &ecdhKEM{curve: ecdh.P384()}
case CurveP521:
kem = &ecdhKEM{curve: elliptic.P521()}
kem = &ecdhKEM{curve: ecdh.P521()}
case CurveX25519:
kem = &x25519KEM{}
kem = &ecdhKEM{curve: ecdh.X25519()}
case CurveX25519Kyber768:
// draft-tls-westerbaan-xyber768d00-03
kem = &concatKEM{kem1: &x25519KEM{}, kem2: &kyberKEM{}}
kem = &concatKEM{kem1: &ecdhKEM{curve: ecdh.X25519()}, kem2: &kyberKEM{}}
case CurveX25519MLKEM768:
// draft-kwiatkowski-tls-ecdhe-mlkem-01
kem = &concatKEM{kem1: &mlkem768KEM{}, kem2: &x25519KEM{}}
kem = &concatKEM{kem1: &mlkem768KEM{}, kem2: &ecdhKEM{curve: ecdh.X25519()}}
default:
return nil, false
}
Expand Down

0 comments on commit 2be18f5

Please sign in to comment.