x/crypto/ssh: implement curve25519-sha256@libssh.org key agreement.
Fixes golang/go#11004. Change-Id: Ic37cf9d620e3397b7ad769ae16abdaee63a7733b Reviewed-on: https://go-review.googlesource.com/13592 Reviewed-by: Adam Langley <agl@golang.org>
This commit is contained in:
@@ -33,6 +33,7 @@ var supportedCiphers = []string{
|
|||||||
// supportedKexAlgos specifies the supported key-exchange algorithms in
|
// supportedKexAlgos specifies the supported key-exchange algorithms in
|
||||||
// preference order.
|
// preference order.
|
||||||
var supportedKexAlgos = []string{
|
var supportedKexAlgos = []string{
|
||||||
|
kexAlgoCurve25519SHA256,
|
||||||
// P384 and P521 are not constant-time yet, but since we don't
|
// P384 and P521 are not constant-time yet, but since we don't
|
||||||
// reuse ephemeral keys, using them for ECDH should be OK.
|
// reuse ephemeral keys, using them for ECDH should be OK.
|
||||||
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
||||||
|
|||||||
+145
-5
@@ -8,18 +8,22 @@ import (
|
|||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
|
"crypto/subtle"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/curve25519"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
|
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
|
||||||
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
|
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
|
||||||
kexAlgoECDH256 = "ecdh-sha2-nistp256"
|
kexAlgoECDH256 = "ecdh-sha2-nistp256"
|
||||||
kexAlgoECDH384 = "ecdh-sha2-nistp384"
|
kexAlgoECDH384 = "ecdh-sha2-nistp384"
|
||||||
kexAlgoECDH521 = "ecdh-sha2-nistp521"
|
kexAlgoECDH521 = "ecdh-sha2-nistp521"
|
||||||
|
kexAlgoCurve25519SHA256 = "curve25519-sha256@libssh.org"
|
||||||
)
|
)
|
||||||
|
|
||||||
// kexResult captures the outcome of a key exchange.
|
// kexResult captures the outcome of a key exchange.
|
||||||
@@ -383,4 +387,140 @@ func init() {
|
|||||||
kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()}
|
kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()}
|
||||||
kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()}
|
kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()}
|
||||||
kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()}
|
kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()}
|
||||||
|
kexAlgoMap[kexAlgoCurve25519SHA256] = &curve25519sha256{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// curve25519sha256 implements the curve25519-sha256@libssh.org key
|
||||||
|
// agreement protocol, as described in
|
||||||
|
// https://git.libssh.org/projects/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt
|
||||||
|
type curve25519sha256 struct{}
|
||||||
|
|
||||||
|
type curve25519KeyPair struct {
|
||||||
|
priv [32]byte
|
||||||
|
pub [32]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kp *curve25519KeyPair) generate(rand io.Reader) error {
|
||||||
|
if _, err := io.ReadFull(rand, kp.priv[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
curve25519.ScalarBaseMult(&kp.pub, &kp.priv)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// curve25519Zeros is just an array of 32 zero bytes so that we have something
|
||||||
|
// convenient to compare against in order to reject curve25519 points with the
|
||||||
|
// wrong order.
|
||||||
|
var curve25519Zeros [32]byte
|
||||||
|
|
||||||
|
func (kex *curve25519sha256) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
||||||
|
var kp curve25519KeyPair
|
||||||
|
if err := kp.generate(rand); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.writePacket(Marshal(&kexECDHInitMsg{kp.pub[:]})); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
packet, err := c.readPacket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var reply kexECDHReplyMsg
|
||||||
|
if err = Unmarshal(packet, &reply); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(reply.EphemeralPubKey) != 32 {
|
||||||
|
return nil, errors.New("ssh: peer's curve25519 public value has wrong length")
|
||||||
|
}
|
||||||
|
|
||||||
|
var servPub, secret [32]byte
|
||||||
|
copy(servPub[:], reply.EphemeralPubKey)
|
||||||
|
curve25519.ScalarMult(&secret, &kp.priv, &servPub)
|
||||||
|
if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 {
|
||||||
|
return nil, errors.New("ssh: peer's curve25519 public value has wrong order")
|
||||||
|
}
|
||||||
|
|
||||||
|
h := crypto.SHA256.New()
|
||||||
|
magics.write(h)
|
||||||
|
writeString(h, reply.HostKey)
|
||||||
|
writeString(h, kp.pub[:])
|
||||||
|
writeString(h, reply.EphemeralPubKey)
|
||||||
|
|
||||||
|
kInt := new(big.Int).SetBytes(secret[:])
|
||||||
|
K := make([]byte, intLength(kInt))
|
||||||
|
marshalInt(K, kInt)
|
||||||
|
h.Write(K)
|
||||||
|
|
||||||
|
return &kexResult{
|
||||||
|
H: h.Sum(nil),
|
||||||
|
K: K,
|
||||||
|
HostKey: reply.HostKey,
|
||||||
|
Signature: reply.Signature,
|
||||||
|
Hash: crypto.SHA256,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kex *curve25519sha256) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
|
||||||
|
packet, err := c.readPacket()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var kexInit kexECDHInitMsg
|
||||||
|
if err = Unmarshal(packet, &kexInit); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(kexInit.ClientPubKey) != 32 {
|
||||||
|
return nil, errors.New("ssh: peer's curve25519 public value has wrong length")
|
||||||
|
}
|
||||||
|
|
||||||
|
var kp curve25519KeyPair
|
||||||
|
if err := kp.generate(rand); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientPub, secret [32]byte
|
||||||
|
copy(clientPub[:], kexInit.ClientPubKey)
|
||||||
|
curve25519.ScalarMult(&secret, &kp.priv, &clientPub)
|
||||||
|
if subtle.ConstantTimeCompare(secret[:], curve25519Zeros[:]) == 1 {
|
||||||
|
return nil, errors.New("ssh: peer's curve25519 public value has wrong order")
|
||||||
|
}
|
||||||
|
|
||||||
|
hostKeyBytes := priv.PublicKey().Marshal()
|
||||||
|
|
||||||
|
h := crypto.SHA256.New()
|
||||||
|
magics.write(h)
|
||||||
|
writeString(h, hostKeyBytes)
|
||||||
|
writeString(h, kexInit.ClientPubKey)
|
||||||
|
writeString(h, kp.pub[:])
|
||||||
|
|
||||||
|
kInt := new(big.Int).SetBytes(secret[:])
|
||||||
|
K := make([]byte, intLength(kInt))
|
||||||
|
marshalInt(K, kInt)
|
||||||
|
h.Write(K)
|
||||||
|
|
||||||
|
H := h.Sum(nil)
|
||||||
|
|
||||||
|
sig, err := signAndMarshal(priv, rand, H)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reply := kexECDHReplyMsg{
|
||||||
|
EphemeralPubKey: kp.pub[:],
|
||||||
|
HostKey: hostKeyBytes,
|
||||||
|
Signature: sig,
|
||||||
|
}
|
||||||
|
if err := c.writePacket(Marshal(&reply)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &kexResult{
|
||||||
|
H: H,
|
||||||
|
K: K,
|
||||||
|
HostKey: hostKeyBytes,
|
||||||
|
Signature: sig,
|
||||||
|
Hash: crypto.SHA256,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user