p2p: make encryption handshake code easier to follow

This mostly changes how information is passed around.
Instead of using many function parameters and return values,
put the entire state in a struct and pass that.

This also adds back derivation of ecdhe-shared-secret. I deleted
it by accident in a previous refactoring.
This commit is contained in:
Felix Lange 2015-03-02 15:26:44 +01:00
parent 2c505efd1e
commit 7d39fd6678
2 changed files with 280 additions and 310 deletions

View File

@ -2,6 +2,7 @@ package p2p
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"crypto/elliptic"
"crypto/rand" "crypto/rand"
"errors" "errors"
"fmt" "fmt"
@ -26,26 +27,26 @@ const (
authMsgLen = sigLen + shaLen + pubLen + shaLen + 1 authMsgLen = sigLen + shaLen + pubLen + shaLen + 1
authRespLen = pubLen + shaLen + 1 authRespLen = pubLen + shaLen + 1
eciesBytes = 65 + 16 + 32 eciesBytes = 65 + 16 + 32
iHSLen = authMsgLen + eciesBytes // size of the final ECIES payload sent as initiator's handshake encAuthMsgLen = authMsgLen + eciesBytes // size of the final ECIES payload sent as initiator's handshake
rHSLen = authRespLen + eciesBytes // size of the final ECIES payload sent as receiver's handshake encAuthRespLen = authRespLen + eciesBytes // size of the final ECIES payload sent as receiver's handshake
) )
// conn represents a remote connection after encryption handshake
// and protocol handshake have completed.
//
// The MsgReadWriter is usually layered as follows:
//
// lockedRW (thread-safety for ReadMsg, WriteMsg)
// rlpxFrameRW (message encoding, encryption, authentication)
// bufio.ReadWriter (buffering)
// net.Conn (network I/O)
//
type conn struct { type conn struct {
MsgReadWriter MsgReadWriter
*protoHandshake *protoHandshake
} }
// encHandshake contains the state of the encryption handshake.
type encHandshake struct {
remoteID discover.NodeID
initiator bool
initNonce, respNonce []byte
dhSharedSecret []byte
randomPrivKey *ecdsa.PrivateKey
remoteRandomPub *ecdsa.PublicKey
}
// secrets represents the connection secrets // secrets represents the connection secrets
// which are negotiated during the encryption handshake. // which are negotiated during the encryption handshake.
type secrets struct { type secrets struct {
@ -64,34 +65,6 @@ type protoHandshake struct {
ID discover.NodeID ID discover.NodeID
} }
// secrets is called after the handshake is completed.
// It extracts the connection secrets from the handshake values.
func (h *encHandshake) secrets(auth, authResp []byte) secrets {
sharedSecret := crypto.Sha3(h.dhSharedSecret, crypto.Sha3(h.respNonce, h.initNonce))
aesSecret := crypto.Sha3(h.dhSharedSecret, sharedSecret)
s := secrets{
RemoteID: h.remoteID,
AES: aesSecret,
MAC: crypto.Sha3(h.dhSharedSecret, aesSecret),
Token: crypto.Sha3(sharedSecret),
}
// setup sha3 instances for the MACs
mac1 := sha3.NewKeccak256()
mac1.Write(xor(s.MAC, h.respNonce))
mac1.Write(auth)
mac2 := sha3.NewKeccak256()
mac2.Write(xor(s.MAC, h.initNonce))
mac2.Write(authResp)
if h.initiator {
s.EgressMAC, s.IngressMAC = mac1, mac2
} else {
s.EgressMAC, s.IngressMAC = mac2, mac1
}
return s
}
// setupConn starts a protocol session on the given connection. // setupConn starts a protocol session on the given connection.
// It runs the encryption handshake and the protocol handshake. // It runs the encryption handshake and the protocol handshake.
// If dial is non-nil, the connection the local node is the initiator. // If dial is non-nil, the connection the local node is the initiator.
@ -104,7 +77,7 @@ func setupConn(fd net.Conn, prv *ecdsa.PrivateKey, our *protoHandshake, dial *di
} }
func setupInboundConn(fd net.Conn, prv *ecdsa.PrivateKey, our *protoHandshake) (*conn, error) { func setupInboundConn(fd net.Conn, prv *ecdsa.PrivateKey, our *protoHandshake) (*conn, error) {
secrets, err := inboundEncHandshake(fd, prv, nil) secrets, err := receiverEncHandshake(fd, prv, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("encryption handshake failed: %v", err) return nil, fmt.Errorf("encryption handshake failed: %v", err)
} }
@ -124,7 +97,7 @@ func setupInboundConn(fd net.Conn, prv *ecdsa.PrivateKey, our *protoHandshake) (
} }
func setupOutboundConn(fd net.Conn, prv *ecdsa.PrivateKey, our *protoHandshake, dial *discover.Node) (*conn, error) { func setupOutboundConn(fd net.Conn, prv *ecdsa.PrivateKey, our *protoHandshake, dial *discover.Node) (*conn, error) {
secrets, err := outboundEncHandshake(fd, prv, dial.ID[:], nil) secrets, err := initiatorEncHandshake(fd, prv, dial.ID, nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("encryption handshake failed: %v", err) return nil, fmt.Errorf("encryption handshake failed: %v", err)
} }
@ -145,14 +118,66 @@ func setupOutboundConn(fd net.Conn, prv *ecdsa.PrivateKey, our *protoHandshake,
return &conn{&lockedRW{wrapped: rw}, rhs}, nil return &conn{&lockedRW{wrapped: rw}, rhs}, nil
} }
// outboundEncHandshake negotiates a session token on conn. // encHandshake contains the state of the encryption handshake.
type encHandshake struct {
initiator bool
remoteID discover.NodeID
remotePub *ecies.PublicKey // remote-pubk
initNonce, respNonce []byte // nonce
randomPrivKey *ecies.PrivateKey // ecdhe-random
remoteRandomPub *ecies.PublicKey // ecdhe-random-pubk
}
// secrets is called after the handshake is completed.
// It extracts the connection secrets from the handshake values.
func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) {
ecdheSecret, err := h.randomPrivKey.GenerateShared(h.remoteRandomPub, sskLen, sskLen)
if err != nil {
return secrets{}, err
}
// derive base secrets from ephemeral key agreement
sharedSecret := crypto.Sha3(ecdheSecret, crypto.Sha3(h.respNonce, h.initNonce))
aesSecret := crypto.Sha3(ecdheSecret, sharedSecret)
s := secrets{
RemoteID: h.remoteID,
AES: aesSecret,
MAC: crypto.Sha3(ecdheSecret, aesSecret),
Token: crypto.Sha3(sharedSecret),
}
// setup sha3 instances for the MACs
mac1 := sha3.NewKeccak256()
mac1.Write(xor(s.MAC, h.respNonce))
mac1.Write(auth)
mac2 := sha3.NewKeccak256()
mac2.Write(xor(s.MAC, h.initNonce))
mac2.Write(authResp)
if h.initiator {
s.EgressMAC, s.IngressMAC = mac1, mac2
} else {
s.EgressMAC, s.IngressMAC = mac2, mac1
}
return s, nil
}
func (h *encHandshake) ecdhShared(prv *ecdsa.PrivateKey) ([]byte, error) {
return ecies.ImportECDSA(prv).GenerateShared(h.remotePub, sskLen, sskLen)
}
// initiatorEncHandshake negotiates a session token on conn.
// it should be called on the dialing side of the connection. // it should be called on the dialing side of the connection.
// //
// privateKey is the local client's private key // prv is the local client's private key.
// remotePublicKey is the remote peer's node ID // token is the token from a previous session with this node.
// sessionToken is the token from a previous session with this node. func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remoteID discover.NodeID, token []byte) (s secrets, err error) {
func outboundEncHandshake(conn io.ReadWriter, prvKey *ecdsa.PrivateKey, remotePublicKey []byte, sessionToken []byte) (s secrets, err error) { h, err := newInitiatorHandshake(remoteID)
auth, initNonce, randomPrivKey, err := authMsg(prvKey, remotePublicKey, sessionToken) if err != nil {
return s, err
}
auth, err := h.authMsg(prv, token)
if err != nil { if err != nil {
return s, err return s, err
} }
@ -160,250 +185,189 @@ func outboundEncHandshake(conn io.ReadWriter, prvKey *ecdsa.PrivateKey, remotePu
return s, err return s, err
} }
response := make([]byte, rHSLen) response := make([]byte, encAuthRespLen)
if _, err = io.ReadFull(conn, response); err != nil { if _, err = io.ReadFull(conn, response); err != nil {
return s, err return s, err
} }
recNonce, remoteRandomPubKey, _, err := completeHandshake(response, prvKey) if err := h.decodeAuthResp(response, prv); err != nil {
if err != nil {
return s, err return s, err
} }
return h.secrets(auth, response)
h := &encHandshake{
initiator: true,
initNonce: initNonce,
respNonce: recNonce,
randomPrivKey: randomPrivKey,
remoteRandomPub: remoteRandomPubKey,
}
copy(h.remoteID[:], remotePublicKey)
return h.secrets(auth, response), nil
} }
// authMsg creates the initiator handshake. func newInitiatorHandshake(remoteID discover.NodeID) (*encHandshake, error) {
// TODO: change all the names // generate random initiator nonce
func authMsg(prvKey *ecdsa.PrivateKey, remotePubKeyS, sessionToken []byte) ( n := make([]byte, shaLen)
auth, initNonce []byte, if _, err := rand.Read(n); err != nil {
randomPrvKey *ecdsa.PrivateKey, return nil, err
err error,
) {
remotePubKey, err := importPublicKey(remotePubKeyS)
if err != nil {
return
} }
// generate random keypair to use for signing
randpriv, err := ecies.GenerateKey(rand.Reader, crypto.S256(), nil)
if err != nil {
return nil, err
}
rpub, err := remoteID.Pubkey()
if err != nil {
return nil, fmt.Errorf("bad remoteID: %v", err)
}
h := &encHandshake{
initiator: true,
remoteID: remoteID,
remotePub: ecies.ImportECDSAPublic(rpub),
initNonce: n,
randomPrivKey: randpriv,
}
return h, nil
}
// authMsg creates an encrypted initiator handshake message.
func (h *encHandshake) authMsg(prv *ecdsa.PrivateKey, token []byte) ([]byte, error) {
var tokenFlag byte var tokenFlag byte
if sessionToken == nil { if token == nil {
// no session token found means we need to generate shared secret. // no session token found means we need to generate shared secret.
// ecies shared secret is used as initial session token for new peers // ecies shared secret is used as initial session token for new peers
// generate shared key from prv and remote pubkey // generate shared key from prv and remote pubkey
if sessionToken, err = ecies.ImportECDSA(prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil { var err error
return if token, err = h.ecdhShared(prv); err != nil {
return nil, err
} }
} else { } else {
// for known peers, we use stored token from the previous session // for known peers, we use stored token from the previous session
tokenFlag = 0x01 tokenFlag = 0x01
} }
//E(remote-pubk, S(ecdhe-random, sha3(ecdh-shared-secret^nonce)) || H(ecdhe-random-pubk) || pubk || nonce || 0x0) // sign known message:
// E(remote-pubk, S(ecdhe-random, sha3(token^nonce)) || H(ecdhe-random-pubk) || pubk || nonce || 0x1) // ecdh-shared-secret^nonce for new peers
// allocate msgLen long message, // token^nonce for old peers
var msg []byte = make([]byte, authMsgLen) signed := xor(token, h.initNonce)
initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-1] signature, err := crypto.Sign(signed, h.randomPrivKey.ExportECDSA())
if _, err = rand.Read(initNonce); err != nil { if err != nil {
return return nil, err
}
// create known message
// ecdh-shared-secret^nonce for new peers
// token^nonce for old peers
var sharedSecret = xor(sessionToken, initNonce)
// generate random keypair to use for signing
if randomPrvKey, err = crypto.GenerateKey(); err != nil {
return
}
// sign shared secret (message known to both parties): shared-secret
var signature []byte
// signature = sign(ecdhe-random, shared-secret)
// uses secp256k1.Sign
if signature, err = crypto.Sign(sharedSecret, randomPrvKey); err != nil {
return
} }
// message // encode auth message
// signed-shared-secret || H(ecdhe-random-pubk) || pubk || nonce || 0x0 // signature || sha3(ecdhe-random-pubk) || pubk || nonce || token-flag
copy(msg, signature) // copy signed-shared-secret msg := make([]byte, authMsgLen)
// H(ecdhe-random-pubk) n := copy(msg, signature)
var randomPubKey64 []byte n += copy(msg[n:], crypto.Sha3(exportPubkey(&h.randomPrivKey.PublicKey)))
if randomPubKey64, err = exportPublicKey(&randomPrvKey.PublicKey); err != nil { n += copy(msg[n:], crypto.FromECDSAPub(&prv.PublicKey)[1:])
return n += copy(msg[n:], h.initNonce)
} msg[n] = tokenFlag
var pubKey64 []byte
if pubKey64, err = exportPublicKey(&prvKey.PublicKey); err != nil {
return
}
copy(msg[sigLen:sigLen+shaLen], crypto.Sha3(randomPubKey64))
// pubkey copied to the correct segment.
copy(msg[sigLen+shaLen:sigLen+shaLen+pubLen], pubKey64)
// nonce is already in the slice
// stick tokenFlag byte to the end
msg[authMsgLen-1] = tokenFlag
// encrypt using remote-pubk // encrypt auth message using remote-pubk
// auth = eciesEncrypt(remote-pubk, msg) return ecies.Encrypt(rand.Reader, h.remotePub, msg, nil, nil)
if auth, err = crypto.Encrypt(remotePubKey, msg); err != nil {
return
}
return
} }
// completeHandshake is called when the initiator receives an // decodeAuthResp decode an encrypted authentication response message.
// authentication response (aka receiver handshake). It completes the func (h *encHandshake) decodeAuthResp(auth []byte, prv *ecdsa.PrivateKey) error {
// handshake by reading off parameters the remote peer provides needed msg, err := crypto.Decrypt(prv, auth)
// to set up the secure session. if err != nil {
func completeHandshake(auth []byte, prvKey *ecdsa.PrivateKey) ( return fmt.Errorf("could not decrypt auth response (%v)", err)
respNonce []byte,
remoteRandomPubKey *ecdsa.PublicKey,
tokenFlag bool,
err error,
) {
var msg []byte
// they prove that msg is meant for me,
// I prove I possess private key if i can read it
if msg, err = crypto.Decrypt(prvKey, auth); err != nil {
return
} }
h.respNonce = msg[pubLen : pubLen+shaLen]
respNonce = msg[pubLen : pubLen+shaLen] h.remoteRandomPub, err = importPublicKey(msg[:pubLen])
var remoteRandomPubKeyS = msg[:pubLen] if err != nil {
if remoteRandomPubKey, err = importPublicKey(remoteRandomPubKeyS); err != nil { return err
return
} }
if msg[authRespLen-1] == 0x01 { // ignore token flag for now
tokenFlag = true return nil
}
return
} }
// inboundEncHandshake negotiates a session token on conn. // receiverEncHandshake negotiates a session token on conn.
// it should be called on the listening side of the connection. // it should be called on the listening side of the connection.
// //
// privateKey is the local client's private key // prv is the local client's private key.
// sessionToken is the token from a previous session with this node. // token is the token from a previous session with this node.
func inboundEncHandshake(conn io.ReadWriter, prvKey *ecdsa.PrivateKey, sessionToken []byte) (s secrets, err error) { func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, token []byte) (s secrets, err error) {
// we are listening connection. we are responders in the // read remote auth sent by initiator.
// handshake. Extract info from the authentication. The initiator auth := make([]byte, encAuthMsgLen)
// starts by sending us a handshake that we need to respond to. so
// we read auth message first, then respond.
auth := make([]byte, iHSLen)
if _, err := io.ReadFull(conn, auth); err != nil { if _, err := io.ReadFull(conn, auth); err != nil {
return s, err return s, err
} }
response, recNonce, initNonce, remotePubKey, randomPrivKey, remoteRandomPubKey, err := authResp(auth, sessionToken, prvKey) h, err := decodeAuthMsg(prv, token, auth)
if err != nil { if err != nil {
return s, err return s, err
} }
if _, err = conn.Write(response); err != nil {
// send auth response
resp, err := h.authResp(prv, token)
if err != nil {
return s, err
}
if _, err = conn.Write(resp); err != nil {
return s, err return s, err
} }
h := &encHandshake{ return h.secrets(auth, resp)
initiator: false,
initNonce: initNonce,
respNonce: recNonce,
randomPrivKey: randomPrivKey,
remoteRandomPub: remoteRandomPubKey,
}
copy(h.remoteID[:], remotePubKey)
return h.secrets(auth, response), err
} }
// authResp is called by peer if it accepted (but not func decodeAuthMsg(prv *ecdsa.PrivateKey, token []byte, auth []byte) (*encHandshake, error) {
// initiated) the connection from the remote. It is passed the initiator var err error
// handshake received and the session token belonging to the h := new(encHandshake)
// remote initiator.
//
// The first return value is the authentication response (aka receiver
// handshake) that is to be sent to the remote initiator.
func authResp(auth, sessionToken []byte, prvKey *ecdsa.PrivateKey) (
authResp, respNonce, initNonce, remotePubKeyS []byte,
randomPrivKey *ecdsa.PrivateKey,
remoteRandomPubKey *ecdsa.PublicKey,
err error,
) {
// they prove that msg is meant for me,
// I prove I possess private key if i can read it
msg, err := crypto.Decrypt(prvKey, auth)
if err != nil {
return
}
remotePubKeyS = msg[sigLen+shaLen : sigLen+shaLen+pubLen]
remotePubKey, _ := importPublicKey(remotePubKeyS)
var tokenFlag byte
if sessionToken == nil {
// no session token found means we need to generate shared secret.
// ecies shared secret is used as initial session token for new peers
// generate shared key from prv and remote pubkey
if sessionToken, err = ecies.ImportECDSA(prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil {
return
}
// tokenFlag = 0x00 // redundant
} else {
// for known peers, we use stored token from the previous session
tokenFlag = 0x01
}
// the initiator nonce is read off the end of the message
initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-1]
// I prove that i own prv key (to derive shared secret, and read
// nonce off encrypted msg) and that I own shared secret they
// prove they own the private key belonging to ecdhe-random-pubk
// we can now reconstruct the signed message and recover the peers
// pubkey
var signedMsg = xor(sessionToken, initNonce)
var remoteRandomPubKeyS []byte
if remoteRandomPubKeyS, err = secp256k1.RecoverPubkey(signedMsg, msg[:sigLen]); err != nil {
return
}
// convert to ECDSA standard
if remoteRandomPubKey, err = importPublicKey(remoteRandomPubKeyS); err != nil {
return
}
// now we find ourselves a long task too, fill it random
var resp = make([]byte, authRespLen)
// generate shaLen long nonce
respNonce = resp[pubLen : pubLen+shaLen]
if _, err = rand.Read(respNonce); err != nil {
return
}
// generate random keypair for session // generate random keypair for session
if randomPrivKey, err = crypto.GenerateKey(); err != nil { h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil)
return if err != nil {
return nil, err
} }
// generate random nonce
h.respNonce = make([]byte, shaLen)
if _, err = rand.Read(h.respNonce); err != nil {
return nil, err
}
msg, err := crypto.Decrypt(prv, auth)
if err != nil {
return nil, fmt.Errorf("could not decrypt auth message (%v)", err)
}
// decode message parameters
// signature || sha3(ecdhe-random-pubk) || pubk || nonce || token-flag
h.initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-1]
copy(h.remoteID[:], msg[sigLen+shaLen:sigLen+shaLen+pubLen])
rpub, err := h.remoteID.Pubkey()
if err != nil {
return nil, fmt.Errorf("bad remoteID: %#v", err)
}
h.remotePub = ecies.ImportECDSAPublic(rpub)
// recover remote random pubkey from signed message.
if token == nil {
// TODO: it is an error if the initiator has a token and we don't. check that.
// no session token means we need to generate shared secret.
// ecies shared secret is used as initial session token for new peers.
// generate shared key from prv and remote pubkey.
if token, err = h.ecdhShared(prv); err != nil {
return nil, err
}
}
signedMsg := xor(token, h.initNonce)
remoteRandomPub, err := secp256k1.RecoverPubkey(signedMsg, msg[:sigLen])
if err != nil {
return nil, err
}
h.remoteRandomPub, _ = importPublicKey(remoteRandomPub)
return h, nil
}
// authResp generates the encrypted authentication response message.
func (h *encHandshake) authResp(prv *ecdsa.PrivateKey, token []byte) ([]byte, error) {
// responder auth message // responder auth message
// E(remote-pubk, ecdhe-random-pubk || nonce || 0x0) // E(remote-pubk, ecdhe-random-pubk || nonce || 0x0)
var randomPubKeyS []byte resp := make([]byte, authRespLen)
if randomPubKeyS, err = exportPublicKey(&randomPrivKey.PublicKey); err != nil { n := copy(resp, exportPubkey(&h.randomPrivKey.PublicKey))
return n += copy(resp[n:], h.respNonce)
if token == nil {
resp[n] = 0
} else {
resp[n] = 1
} }
copy(resp[:pubLen], randomPubKeyS)
// nonce is already in the slice
resp[authRespLen-1] = tokenFlag
// encrypt using remote-pubk // encrypt using remote-pubk
// auth = eciesEncrypt(remote-pubk, msg) return ecies.Encrypt(rand.Reader, h.remotePub, resp, nil, nil)
// why not encrypt with ecdhe-random-remote
if authResp, err = crypto.Encrypt(remotePubKey, resp); err != nil {
return
}
return
} }
// importPublicKey unmarshals 512 bit public keys. // importPublicKey unmarshals 512 bit public keys.
func importPublicKey(pubKey []byte) (pubKeyEC *ecdsa.PublicKey, err error) { func importPublicKey(pubKey []byte) (*ecies.PublicKey, error) {
var pubKey65 []byte var pubKey65 []byte
switch len(pubKey) { switch len(pubKey) {
case 64: case 64:
@ -414,14 +378,15 @@ func importPublicKey(pubKey []byte) (pubKeyEC *ecdsa.PublicKey, err error) {
default: default:
return nil, fmt.Errorf("invalid public key length %v (expect 64/65)", len(pubKey)) return nil, fmt.Errorf("invalid public key length %v (expect 64/65)", len(pubKey))
} }
return crypto.ToECDSAPub(pubKey65), nil // TODO: fewer pointless conversions
return ecies.ImportECDSAPublic(crypto.ToECDSAPub(pubKey65)), nil
} }
func exportPublicKey(pubKeyEC *ecdsa.PublicKey) (pubKey []byte, err error) { func exportPubkey(pub *ecies.PublicKey) []byte {
if pubKeyEC == nil { if pub == nil {
return nil, fmt.Errorf("no ECDSA public key given") panic("nil pubkey")
} }
return crypto.FromECDSAPub(pubKeyEC)[1:], nil return elliptic.Marshal(pub.Curve, pub.X, pub.Y)[1:]
} }
func xor(one, other []byte) (xor []byte) { func xor(one, other []byte) (xor []byte) {

View File

@ -2,51 +2,18 @@ package p2p
import ( import (
"bytes" "bytes"
"crypto/rand"
"fmt"
"net" "net"
"reflect" "reflect"
"testing" "testing"
"time"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/ecies" "github.com/ethereum/go-ethereum/crypto/ecies"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
) )
func TestPublicKeyEncoding(t *testing.T) {
prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader)
pub0 := &prv0.PublicKey
pub0s := crypto.FromECDSAPub(pub0)
pub1, err := importPublicKey(pub0s)
if err != nil {
t.Errorf("%v", err)
}
eciesPub1 := ecies.ImportECDSAPublic(pub1)
if eciesPub1 == nil {
t.Errorf("invalid ecdsa public key")
}
pub1s, err := exportPublicKey(pub1)
if err != nil {
t.Errorf("%v", err)
}
if len(pub1s) != 64 {
t.Errorf("wrong length expect 64, got", len(pub1s))
}
pub2, err := importPublicKey(pub1s)
if err != nil {
t.Errorf("%v", err)
}
pub2s, err := exportPublicKey(pub2)
if err != nil {
t.Errorf("%v", err)
}
if !bytes.Equal(pub1s, pub2s) {
t.Errorf("exports dont match")
}
pub2sEC := crypto.FromECDSAPub(pub2)
if !bytes.Equal(pub0s, pub2sEC) {
t.Errorf("exports dont match")
}
}
func TestSharedSecret(t *testing.T) { func TestSharedSecret(t *testing.T) {
prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader) prv0, _ := crypto.GenerateKey() // = ecdsa.GenerateKey(crypto.S256(), rand.Reader)
pub0 := &prv0.PublicKey pub0 := &prv0.PublicKey
@ -68,46 +35,84 @@ func TestSharedSecret(t *testing.T) {
} }
func TestEncHandshake(t *testing.T) { func TestEncHandshake(t *testing.T) {
defer testlog(t).detach() for i := 0; i < 20; i++ {
start := time.Now()
if err := testEncHandshake(nil); err != nil {
t.Fatalf("i=%d %v", i, err)
}
t.Logf("(without token) %d %v\n", i+1, time.Since(start))
}
prv0, _ := crypto.GenerateKey() for i := 0; i < 20; i++ {
prv1, _ := crypto.GenerateKey() tok := make([]byte, shaLen)
rw0, rw1 := net.Pipe() rand.Reader.Read(tok)
secrets := make(chan secrets) start := time.Now()
if err := testEncHandshake(tok); err != nil {
t.Fatalf("i=%d %v", i, err)
}
t.Logf("(with token) %d %v\n", i+1, time.Since(start))
}
}
func testEncHandshake(token []byte) error {
type result struct {
side string
s secrets
err error
}
var (
prv0, _ = crypto.GenerateKey()
prv1, _ = crypto.GenerateKey()
rw0, rw1 = net.Pipe()
output = make(chan result)
)
go func() { go func() {
pub1s, _ := exportPublicKey(&prv1.PublicKey) r := result{side: "initiator"}
s, err := outboundEncHandshake(rw0, prv0, pub1s, nil) defer func() { output <- r }()
if err != nil {
t.Errorf("outbound side error: %v", err) pub1s := discover.PubkeyID(&prv1.PublicKey)
r.s, r.err = initiatorEncHandshake(rw0, prv0, pub1s, token)
if r.err != nil {
return
} }
id1 := discover.PubkeyID(&prv1.PublicKey) id1 := discover.PubkeyID(&prv1.PublicKey)
if s.RemoteID != id1 { if r.s.RemoteID != id1 {
t.Errorf("outbound side remote ID mismatch") r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.s.RemoteID, id1)
} }
secrets <- s
}() }()
go func() { go func() {
s, err := inboundEncHandshake(rw1, prv1, nil) r := result{side: "receiver"}
if err != nil { defer func() { output <- r }()
t.Errorf("inbound side error: %v", err)
r.s, r.err = receiverEncHandshake(rw1, prv1, token)
if r.err != nil {
return
} }
id0 := discover.PubkeyID(&prv0.PublicKey) id0 := discover.PubkeyID(&prv0.PublicKey)
if s.RemoteID != id0 { if r.s.RemoteID != id0 {
t.Errorf("inbound side remote ID mismatch") r.err = fmt.Errorf("remote ID mismatch: got %v, want: %v", r.s.RemoteID, id0)
} }
secrets <- s
}() }()
// get computed secrets from both sides // wait for results from both sides
t1, t2 := <-secrets, <-secrets r1, r2 := <-output, <-output
// don't compare remote node IDs
t1.RemoteID, t2.RemoteID = discover.NodeID{}, discover.NodeID{} if r1.err != nil {
// flip MACs on one of them so they compare equal return fmt.Errorf("%s side error: %v", r1.side, r1.err)
t1.EgressMAC, t1.IngressMAC = t1.IngressMAC, t1.EgressMAC
if !reflect.DeepEqual(t1, t2) {
t.Errorf("secrets mismatch:\n t1: %#v\n t2: %#v", t1, t2)
} }
if r2.err != nil {
return fmt.Errorf("%s side error: %v", r2.side, r2.err)
}
// don't compare remote node IDs
r1.s.RemoteID, r2.s.RemoteID = discover.NodeID{}, discover.NodeID{}
// flip MACs on one of them so they compare equal
r1.s.EgressMAC, r1.s.IngressMAC = r1.s.IngressMAC, r1.s.EgressMAC
if !reflect.DeepEqual(r1.s, r2.s) {
return fmt.Errorf("secrets mismatch:\n t1: %#v\n t2: %#v", r1.s, r2.s)
}
return nil
} }
func TestSetupConn(t *testing.T) { func TestSetupConn(t *testing.T) {