|
|
@ -24,11 +24,14 @@ import ( |
|
|
|
"crypto/elliptic" |
|
|
|
"crypto/elliptic" |
|
|
|
"crypto/hmac" |
|
|
|
"crypto/hmac" |
|
|
|
"crypto/rand" |
|
|
|
"crypto/rand" |
|
|
|
|
|
|
|
"encoding/binary" |
|
|
|
"errors" |
|
|
|
"errors" |
|
|
|
"fmt" |
|
|
|
"fmt" |
|
|
|
"hash" |
|
|
|
"hash" |
|
|
|
"io" |
|
|
|
"io" |
|
|
|
|
|
|
|
mrand "math/rand" |
|
|
|
"net" |
|
|
|
"net" |
|
|
|
|
|
|
|
"os" |
|
|
|
"sync" |
|
|
|
"sync" |
|
|
|
"time" |
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
|
@ -51,9 +54,10 @@ 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 |
|
|
|
eciesOverhead = 65 /* pubkey */ + 16 /* IV */ + 32 /* MAC */ |
|
|
|
encAuthMsgLen = authMsgLen + eciesBytes // size of the final ECIES payload sent as initiator's handshake
|
|
|
|
|
|
|
|
encAuthRespLen = authRespLen + eciesBytes // size of the final ECIES payload sent as receiver's handshake
|
|
|
|
encAuthMsgLen = authMsgLen + eciesOverhead // size of encrypted pre-EIP-8 initiator handshake
|
|
|
|
|
|
|
|
encAuthRespLen = authRespLen + eciesOverhead // size of encrypted pre-EIP-8 handshake reply
|
|
|
|
|
|
|
|
|
|
|
|
// total timeout for encryption handshake and protocol
|
|
|
|
// total timeout for encryption handshake and protocol
|
|
|
|
// handshake in both directions.
|
|
|
|
// handshake in both directions.
|
|
|
@ -151,10 +155,6 @@ func readProtocolHandshake(rw MsgReader, our *protoHandshake) (*protoHandshake, |
|
|
|
if err := msg.Decode(&hs); err != nil { |
|
|
|
if err := msg.Decode(&hs); err != nil { |
|
|
|
return nil, err |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
// validate handshake info
|
|
|
|
|
|
|
|
if hs.Version != our.Version { |
|
|
|
|
|
|
|
return nil, DiscIncompatibleVersion |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (hs.ID == discover.NodeID{}) { |
|
|
|
if (hs.ID == discover.NodeID{}) { |
|
|
|
return nil, DiscInvalidIdentity |
|
|
|
return nil, DiscInvalidIdentity |
|
|
|
} |
|
|
|
} |
|
|
@ -200,6 +200,29 @@ type secrets struct { |
|
|
|
Token []byte |
|
|
|
Token []byte |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// RLPx v4 handshake auth (defined in EIP-8).
|
|
|
|
|
|
|
|
type authMsgV4 struct { |
|
|
|
|
|
|
|
gotPlain bool // whether read packet had plain format.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Signature [sigLen]byte |
|
|
|
|
|
|
|
InitiatorPubkey [pubLen]byte |
|
|
|
|
|
|
|
Nonce [shaLen]byte |
|
|
|
|
|
|
|
Version uint |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Ignore additional fields (forward-compatibility)
|
|
|
|
|
|
|
|
Rest []rlp.RawValue `rlp:"tail"` |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// RLPx v4 handshake response (defined in EIP-8).
|
|
|
|
|
|
|
|
type authRespV4 struct { |
|
|
|
|
|
|
|
RandomPubkey [pubLen]byte |
|
|
|
|
|
|
|
Nonce [shaLen]byte |
|
|
|
|
|
|
|
Version uint |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Ignore additional fields (forward-compatibility)
|
|
|
|
|
|
|
|
Rest []rlp.RawValue `rlp:"tail"` |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// secrets is called after the handshake is completed.
|
|
|
|
// secrets is called after the handshake is completed.
|
|
|
|
// It extracts the connection secrets from the handshake values.
|
|
|
|
// It extracts the connection secrets from the handshake values.
|
|
|
|
func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) { |
|
|
|
func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) { |
|
|
@ -215,7 +238,6 @@ func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) { |
|
|
|
RemoteID: h.remoteID, |
|
|
|
RemoteID: h.remoteID, |
|
|
|
AES: aesSecret, |
|
|
|
AES: aesSecret, |
|
|
|
MAC: crypto.Sha3(ecdheSecret, aesSecret), |
|
|
|
MAC: crypto.Sha3(ecdheSecret, aesSecret), |
|
|
|
Token: crypto.Sha3(sharedSecret), |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// setup sha3 instances for the MACs
|
|
|
|
// setup sha3 instances for the MACs
|
|
|
@ -234,114 +256,89 @@ func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) { |
|
|
|
return s, nil |
|
|
|
return s, nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (h *encHandshake) ecdhShared(prv *ecdsa.PrivateKey) ([]byte, error) { |
|
|
|
// staticSharedSecret returns the static shared secret, the result
|
|
|
|
|
|
|
|
// of key agreement between the local and remote static node key.
|
|
|
|
|
|
|
|
func (h *encHandshake) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error) { |
|
|
|
return ecies.ImportECDSA(prv).GenerateShared(h.remotePub, sskLen, sskLen) |
|
|
|
return ecies.ImportECDSA(prv).GenerateShared(h.remotePub, sskLen, sskLen) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var configSendEIP = os.Getenv("RLPX_EIP8") != "" |
|
|
|
|
|
|
|
|
|
|
|
// initiatorEncHandshake negotiates a session token on conn.
|
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// prv is the local client's private key.
|
|
|
|
// prv is the local client's private key.
|
|
|
|
// token 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 initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remoteID discover.NodeID, token []byte) (s secrets, err error) { |
|
|
|
h, err := newInitiatorHandshake(remoteID) |
|
|
|
h := &encHandshake{initiator: true, remoteID: remoteID} |
|
|
|
|
|
|
|
authMsg, err := h.makeAuthMsg(prv, token) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return s, err |
|
|
|
return s, err |
|
|
|
} |
|
|
|
} |
|
|
|
auth, err := h.authMsg(prv, token) |
|
|
|
var authPacket []byte |
|
|
|
|
|
|
|
if configSendEIP { |
|
|
|
|
|
|
|
authPacket, err = sealEIP8(authMsg, h) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
authPacket, err = authMsg.sealPlain(h) |
|
|
|
|
|
|
|
} |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return s, err |
|
|
|
return s, err |
|
|
|
} |
|
|
|
} |
|
|
|
if _, err = conn.Write(auth); err != nil { |
|
|
|
if _, err = conn.Write(authPacket); err != nil { |
|
|
|
return s, err |
|
|
|
return s, err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
response := make([]byte, encAuthRespLen) |
|
|
|
authRespMsg := new(authRespV4) |
|
|
|
if _, err = io.ReadFull(conn, response); err != nil { |
|
|
|
authRespPacket, err := readHandshakeMsg(authRespMsg, encAuthRespLen, prv, conn) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
return s, err |
|
|
|
return s, err |
|
|
|
} |
|
|
|
} |
|
|
|
if err := h.decodeAuthResp(response, prv); err != nil { |
|
|
|
if err := h.handleAuthResp(authRespMsg); err != nil { |
|
|
|
return s, err |
|
|
|
return s, err |
|
|
|
} |
|
|
|
} |
|
|
|
return h.secrets(auth, response) |
|
|
|
return h.secrets(authPacket, authRespPacket) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func newInitiatorHandshake(remoteID discover.NodeID) (*encHandshake, error) { |
|
|
|
// makeAuthMsg creates the initiator handshake message.
|
|
|
|
rpub, err := remoteID.Pubkey() |
|
|
|
func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey, token []byte) (*authMsgV4, error) { |
|
|
|
|
|
|
|
rpub, err := h.remoteID.Pubkey() |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return nil, fmt.Errorf("bad remoteID: %v", err) |
|
|
|
return nil, fmt.Errorf("bad remoteID: %v", err) |
|
|
|
} |
|
|
|
} |
|
|
|
// generate random initiator nonce
|
|
|
|
h.remotePub = ecies.ImportECDSAPublic(rpub) |
|
|
|
n := make([]byte, shaLen) |
|
|
|
// Generate random initiator nonce.
|
|
|
|
if _, err := rand.Read(n); err != nil { |
|
|
|
h.initNonce = make([]byte, shaLen) |
|
|
|
|
|
|
|
if _, err := rand.Read(h.initNonce); err != nil { |
|
|
|
return nil, err |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
// generate random keypair to use for signing
|
|
|
|
// Generate random keypair to for ECDH.
|
|
|
|
randpriv, err := ecies.GenerateKey(rand.Reader, secp256k1.S256(), nil) |
|
|
|
h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, secp256k1.S256(), nil) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return nil, err |
|
|
|
return nil, 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 |
|
|
|
|
|
|
|
if token == 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
|
|
|
|
|
|
|
|
var err error |
|
|
|
|
|
|
|
if token, err = h.ecdhShared(prv); err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// for known peers, we use stored token from the previous session
|
|
|
|
|
|
|
|
tokenFlag = 0x01 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// sign known message:
|
|
|
|
// Sign known message: static-shared-secret ^ nonce
|
|
|
|
// ecdh-shared-secret^nonce for new peers
|
|
|
|
token, err = h.staticSharedSecret(prv) |
|
|
|
// token^nonce for old peers
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
signed := xor(token, h.initNonce) |
|
|
|
signed := xor(token, h.initNonce) |
|
|
|
signature, err := crypto.Sign(signed, h.randomPrivKey.ExportECDSA()) |
|
|
|
signature, err := crypto.Sign(signed, h.randomPrivKey.ExportECDSA()) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return nil, err |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// encode auth message
|
|
|
|
msg := new(authMsgV4) |
|
|
|
// signature || sha3(ecdhe-random-pubk) || pubk || nonce || token-flag
|
|
|
|
copy(msg.Signature[:], signature) |
|
|
|
msg := make([]byte, authMsgLen) |
|
|
|
copy(msg.InitiatorPubkey[:], crypto.FromECDSAPub(&prv.PublicKey)[1:]) |
|
|
|
n := copy(msg, signature) |
|
|
|
copy(msg.Nonce[:], h.initNonce) |
|
|
|
n += copy(msg[n:], crypto.Sha3(exportPubkey(&h.randomPrivKey.PublicKey))) |
|
|
|
msg.Version = 4 |
|
|
|
n += copy(msg[n:], crypto.FromECDSAPub(&prv.PublicKey)[1:]) |
|
|
|
return msg, nil |
|
|
|
n += copy(msg[n:], h.initNonce) |
|
|
|
|
|
|
|
msg[n] = tokenFlag |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// encrypt auth message using remote-pubk
|
|
|
|
|
|
|
|
return ecies.Encrypt(rand.Reader, h.remotePub, msg, nil, nil) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// decodeAuthResp decode an encrypted authentication response message.
|
|
|
|
func (h *encHandshake) handleAuthResp(msg *authRespV4) (err error) { |
|
|
|
func (h *encHandshake) decodeAuthResp(auth []byte, prv *ecdsa.PrivateKey) error { |
|
|
|
h.respNonce = msg.Nonce[:] |
|
|
|
msg, err := crypto.Decrypt(prv, auth) |
|
|
|
h.remoteRandomPub, err = importPublicKey(msg.RandomPubkey[:]) |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
return fmt.Errorf("could not decrypt auth response (%v)", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
h.respNonce = msg[pubLen : pubLen+shaLen] |
|
|
|
|
|
|
|
h.remoteRandomPub, err = importPublicKey(msg[:pubLen]) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// ignore token flag for now
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// receiverEncHandshake negotiates a session token on conn.
|
|
|
|
// receiverEncHandshake negotiates a session token on conn.
|
|
|
@ -350,99 +347,165 @@ func (h *encHandshake) decodeAuthResp(auth []byte, prv *ecdsa.PrivateKey) error |
|
|
|
// prv is the local client's private key.
|
|
|
|
// prv is the local client's private key.
|
|
|
|
// token is the token from a previous session with this node.
|
|
|
|
// token is the token from a previous session with this node.
|
|
|
|
func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, token []byte) (s secrets, err error) { |
|
|
|
func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, token []byte) (s secrets, err error) { |
|
|
|
// read remote auth sent by initiator.
|
|
|
|
authMsg := new(authMsgV4) |
|
|
|
auth := make([]byte, encAuthMsgLen) |
|
|
|
authPacket, err := readHandshakeMsg(authMsg, encAuthMsgLen, prv, conn) |
|
|
|
if _, err := io.ReadFull(conn, auth); err != nil { |
|
|
|
if err != nil { |
|
|
|
return s, err |
|
|
|
return s, err |
|
|
|
} |
|
|
|
} |
|
|
|
h, err := decodeAuthMsg(prv, token, auth) |
|
|
|
h := new(encHandshake) |
|
|
|
if err != nil { |
|
|
|
if err := h.handleAuthMsg(authMsg, prv); err != nil { |
|
|
|
return s, err |
|
|
|
return s, err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// send auth response
|
|
|
|
authRespMsg, err := h.makeAuthResp() |
|
|
|
resp, err := h.authResp(prv, token) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return s, err |
|
|
|
return s, err |
|
|
|
} |
|
|
|
} |
|
|
|
if _, err = conn.Write(resp); err != nil { |
|
|
|
var authRespPacket []byte |
|
|
|
return s, err |
|
|
|
if authMsg.gotPlain { |
|
|
|
|
|
|
|
authRespPacket, err = authRespMsg.sealPlain(h) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
authRespPacket, err = sealEIP8(authRespMsg, h) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return h.secrets(auth, resp) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func decodeAuthMsg(prv *ecdsa.PrivateKey, token []byte, auth []byte) (*encHandshake, error) { |
|
|
|
|
|
|
|
var err error |
|
|
|
|
|
|
|
h := new(encHandshake) |
|
|
|
|
|
|
|
// generate random keypair for session
|
|
|
|
|
|
|
|
h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, secp256k1.S256(), nil) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return nil, err |
|
|
|
return s, err |
|
|
|
} |
|
|
|
|
|
|
|
// generate random nonce
|
|
|
|
|
|
|
|
h.respNonce = make([]byte, shaLen) |
|
|
|
|
|
|
|
if _, err = rand.Read(h.respNonce); err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if _, err = conn.Write(authRespPacket); err != nil { |
|
|
|
msg, err := crypto.Decrypt(prv, auth) |
|
|
|
return s, err |
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, fmt.Errorf("could not decrypt auth message (%v)", err) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return h.secrets(authPacket, authRespPacket) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// decode message parameters
|
|
|
|
func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error { |
|
|
|
// signature || sha3(ecdhe-random-pubk) || pubk || nonce || token-flag
|
|
|
|
// Import the remote identity.
|
|
|
|
h.initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-1] |
|
|
|
h.initNonce = msg.Nonce[:] |
|
|
|
copy(h.remoteID[:], msg[sigLen+shaLen:sigLen+shaLen+pubLen]) |
|
|
|
h.remoteID = msg.InitiatorPubkey |
|
|
|
rpub, err := h.remoteID.Pubkey() |
|
|
|
rpub, err := h.remoteID.Pubkey() |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return nil, fmt.Errorf("bad remoteID: %#v", err) |
|
|
|
return fmt.Errorf("bad remoteID: %#v", err) |
|
|
|
} |
|
|
|
} |
|
|
|
h.remotePub = ecies.ImportECDSAPublic(rpub) |
|
|
|
h.remotePub = ecies.ImportECDSAPublic(rpub) |
|
|
|
|
|
|
|
|
|
|
|
// recover remote random pubkey from signed message.
|
|
|
|
// Generate random keypair for ECDH.
|
|
|
|
if token == nil { |
|
|
|
// If a private key is already set, use it instead of generating one (for testing).
|
|
|
|
// TODO: it is an error if the initiator has a token and we don't. check that.
|
|
|
|
if h.randomPrivKey == nil { |
|
|
|
|
|
|
|
h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, secp256k1.S256(), nil) |
|
|
|
// no session token means we need to generate shared secret.
|
|
|
|
if err != nil { |
|
|
|
// ecies shared secret is used as initial session token for new peers.
|
|
|
|
return err |
|
|
|
// generate shared key from prv and remote pubkey.
|
|
|
|
|
|
|
|
if token, err = h.ecdhShared(prv); err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check the signature.
|
|
|
|
|
|
|
|
token, err := h.staticSharedSecret(prv) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
} |
|
|
|
signedMsg := xor(token, h.initNonce) |
|
|
|
signedMsg := xor(token, h.initNonce) |
|
|
|
remoteRandomPub, err := secp256k1.RecoverPubkey(signedMsg, msg[:sigLen]) |
|
|
|
remoteRandomPub, err := secp256k1.RecoverPubkey(signedMsg, msg.Signature[:]) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
h.remoteRandomPub, _ = importPublicKey(remoteRandomPub) |
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (h *encHandshake) makeAuthResp() (msg *authRespV4, err error) { |
|
|
|
|
|
|
|
// Generate random nonce.
|
|
|
|
|
|
|
|
h.respNonce = make([]byte, shaLen) |
|
|
|
|
|
|
|
if _, err = rand.Read(h.respNonce); err != nil { |
|
|
|
return nil, err |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// validate the sha3 of recovered pubkey
|
|
|
|
msg = new(authRespV4) |
|
|
|
remoteRandomPubMAC := msg[sigLen : sigLen+shaLen] |
|
|
|
copy(msg.Nonce[:], h.respNonce) |
|
|
|
shaRemoteRandomPub := crypto.Sha3(remoteRandomPub[1:]) |
|
|
|
copy(msg.RandomPubkey[:], exportPubkey(&h.randomPrivKey.PublicKey)) |
|
|
|
if !bytes.Equal(remoteRandomPubMAC, shaRemoteRandomPub) { |
|
|
|
msg.Version = 4 |
|
|
|
return nil, fmt.Errorf("sha3 of recovered ephemeral pubkey does not match checksum in auth message") |
|
|
|
return msg, nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (msg *authMsgV4) sealPlain(h *encHandshake) ([]byte, error) { |
|
|
|
|
|
|
|
buf := make([]byte, authMsgLen) |
|
|
|
|
|
|
|
n := copy(buf, msg.Signature[:]) |
|
|
|
|
|
|
|
n += copy(buf[n:], crypto.Sha3(exportPubkey(&h.randomPrivKey.PublicKey))) |
|
|
|
|
|
|
|
n += copy(buf[n:], msg.InitiatorPubkey[:]) |
|
|
|
|
|
|
|
n += copy(buf[n:], msg.Nonce[:]) |
|
|
|
|
|
|
|
buf[n] = 0 // token-flag
|
|
|
|
|
|
|
|
return ecies.Encrypt(rand.Reader, h.remotePub, buf, nil, nil) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (msg *authMsgV4) decodePlain(input []byte) { |
|
|
|
|
|
|
|
n := copy(msg.Signature[:], input) |
|
|
|
|
|
|
|
n += shaLen // skip sha3(initiator-ephemeral-pubk)
|
|
|
|
|
|
|
|
n += copy(msg.InitiatorPubkey[:], input[n:]) |
|
|
|
|
|
|
|
n += copy(msg.Nonce[:], input[n:]) |
|
|
|
|
|
|
|
msg.Version = 4 |
|
|
|
|
|
|
|
msg.gotPlain = true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (msg *authRespV4) sealPlain(hs *encHandshake) ([]byte, error) { |
|
|
|
|
|
|
|
buf := make([]byte, authRespLen) |
|
|
|
|
|
|
|
n := copy(buf, msg.RandomPubkey[:]) |
|
|
|
|
|
|
|
n += copy(buf[n:], msg.Nonce[:]) |
|
|
|
|
|
|
|
return ecies.Encrypt(rand.Reader, hs.remotePub, buf, nil, nil) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (msg *authRespV4) decodePlain(input []byte) { |
|
|
|
|
|
|
|
n := copy(msg.RandomPubkey[:], input) |
|
|
|
|
|
|
|
n += copy(msg.Nonce[:], input[n:]) |
|
|
|
|
|
|
|
msg.Version = 4 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var padSpace = make([]byte, 300) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func sealEIP8(msg interface{}, h *encHandshake) ([]byte, error) { |
|
|
|
|
|
|
|
buf := new(bytes.Buffer) |
|
|
|
|
|
|
|
if err := rlp.Encode(buf, msg); err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// pad with random amount of data. the amount needs to be at least 100 bytes to make
|
|
|
|
|
|
|
|
// the message distinguishable from pre-EIP-8 handshakes.
|
|
|
|
|
|
|
|
pad := padSpace[:mrand.Intn(len(padSpace)-100)+100] |
|
|
|
|
|
|
|
buf.Write(pad) |
|
|
|
|
|
|
|
prefix := make([]byte, 2) |
|
|
|
|
|
|
|
binary.BigEndian.PutUint16(prefix, uint16(buf.Len()+eciesOverhead)) |
|
|
|
|
|
|
|
|
|
|
|
h.remoteRandomPub, _ = importPublicKey(remoteRandomPub) |
|
|
|
enc, err := ecies.Encrypt(rand.Reader, h.remotePub, buf.Bytes(), nil, prefix) |
|
|
|
return h, nil |
|
|
|
return append(prefix, enc...), err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// authResp generates the encrypted authentication response message.
|
|
|
|
type plainDecoder interface { |
|
|
|
func (h *encHandshake) authResp(prv *ecdsa.PrivateKey, token []byte) ([]byte, error) { |
|
|
|
decodePlain([]byte) |
|
|
|
// responder auth message
|
|
|
|
} |
|
|
|
// E(remote-pubk, ecdhe-random-pubk || nonce || 0x0)
|
|
|
|
|
|
|
|
resp := make([]byte, authRespLen) |
|
|
|
func readHandshakeMsg(msg plainDecoder, plainSize int, prv *ecdsa.PrivateKey, r io.Reader) ([]byte, error) { |
|
|
|
n := copy(resp, exportPubkey(&h.randomPrivKey.PublicKey)) |
|
|
|
buf := make([]byte, plainSize) |
|
|
|
n += copy(resp[n:], h.respNonce) |
|
|
|
if _, err := io.ReadFull(r, buf); err != nil { |
|
|
|
if token == nil { |
|
|
|
return buf, err |
|
|
|
resp[n] = 0 |
|
|
|
} |
|
|
|
} else { |
|
|
|
// Attempt decoding pre-EIP-8 "plain" format.
|
|
|
|
resp[n] = 1 |
|
|
|
key := ecies.ImportECDSA(prv) |
|
|
|
|
|
|
|
if dec, err := key.Decrypt(rand.Reader, buf, nil, nil); err == nil { |
|
|
|
|
|
|
|
msg.decodePlain(dec) |
|
|
|
|
|
|
|
return buf, nil |
|
|
|
} |
|
|
|
} |
|
|
|
// encrypt using remote-pubk
|
|
|
|
// Could be EIP-8 format, try that.
|
|
|
|
return ecies.Encrypt(rand.Reader, h.remotePub, resp, nil, nil) |
|
|
|
prefix := buf[:2] |
|
|
|
|
|
|
|
size := binary.BigEndian.Uint16(prefix) |
|
|
|
|
|
|
|
if size < uint16(plainSize) { |
|
|
|
|
|
|
|
return buf, fmt.Errorf("size underflow, need at least %d bytes", plainSize) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
buf = append(buf, make([]byte, size-uint16(plainSize)+2)...) |
|
|
|
|
|
|
|
if _, err := io.ReadFull(r, buf[plainSize:]); err != nil { |
|
|
|
|
|
|
|
return buf, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
dec, err := key.Decrypt(rand.Reader, buf[2:], nil, prefix) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return buf, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Can't use rlp.DecodeBytes here because it rejects
|
|
|
|
|
|
|
|
// trailing data (forward-compatibility).
|
|
|
|
|
|
|
|
s := rlp.NewStream(bytes.NewReader(dec), 0) |
|
|
|
|
|
|
|
return buf, s.Decode(msg) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// importPublicKey unmarshals 512 bit public keys.
|
|
|
|
// importPublicKey unmarshals 512 bit public keys.
|
|
|
@ -458,7 +521,11 @@ func importPublicKey(pubKey []byte) (*ecies.PublicKey, error) { |
|
|
|
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)) |
|
|
|
} |
|
|
|
} |
|
|
|
// TODO: fewer pointless conversions
|
|
|
|
// TODO: fewer pointless conversions
|
|
|
|
return ecies.ImportECDSAPublic(crypto.ToECDSAPub(pubKey65)), nil |
|
|
|
pub := crypto.ToECDSAPub(pubKey65) |
|
|
|
|
|
|
|
if pub.X == nil { |
|
|
|
|
|
|
|
return nil, fmt.Errorf("invalid public key") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return ecies.ImportECDSAPublic(pub), nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func exportPubkey(pub *ecies.PublicKey) []byte { |
|
|
|
func exportPubkey(pub *ecies.PublicKey) []byte { |
|
|
|