Initial commit
This commit is contained in:
295
handshake.go
Normal file
295
handshake.go
Normal file
@@ -0,0 +1,295 @@
|
||||
package lafsession
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
)
|
||||
|
||||
const (
|
||||
ProtocolVersion uint16 = 1
|
||||
CipherSuiteX25519ChaCha20Poly1305 uint16 = 1
|
||||
|
||||
msgClientHello byte = 1
|
||||
msgServerHello byte = 2
|
||||
msgClientFinished byte = 3
|
||||
msgServerFinished byte = 4
|
||||
|
||||
ephPubLen = 32
|
||||
idPubLen = 32
|
||||
sigLen = ed25519.SignatureSize
|
||||
|
||||
helloMsgLen = 1 + 2 + 2 + ephPubLen + idPubLen + sigLen
|
||||
|
||||
finishedMsgLen = 1 + sigLen
|
||||
|
||||
transcriptCap = helloMsgLen*2 + finishedMsgLen*2
|
||||
)
|
||||
|
||||
type HandshakeRole int
|
||||
|
||||
const (
|
||||
RoleClient HandshakeRole = iota
|
||||
RoleServer
|
||||
)
|
||||
|
||||
type peerAcceptFunc func(peerPub []byte) bool
|
||||
|
||||
type handshakeState struct {
|
||||
role HandshakeRole
|
||||
identity *Identity
|
||||
acceptPeer peerAcceptFunc
|
||||
|
||||
ephPriv *ecdh.PrivateKey
|
||||
ephPub [ephPubLen]byte
|
||||
|
||||
peerEphPub [ephPubLen]byte
|
||||
peerIDPub [idPubLen]byte
|
||||
hasPeer bool
|
||||
|
||||
transcript []byte
|
||||
hasher hash.Hash
|
||||
hashBuf [32]byte
|
||||
labelBuf [1 + 32]byte
|
||||
msgBuf []byte
|
||||
|
||||
version uint16
|
||||
suite uint16
|
||||
}
|
||||
|
||||
func newHandshake(role HandshakeRole, id *Identity, accept peerAcceptFunc) (*handshakeState, error) {
|
||||
curve := ecdh.X25519()
|
||||
eph, err := curve.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := &handshakeState{
|
||||
role: role,
|
||||
identity: id,
|
||||
acceptPeer: accept,
|
||||
ephPriv: eph,
|
||||
transcript: make([]byte, 0, transcriptCap),
|
||||
hasher: sha256.New(),
|
||||
msgBuf: make([]byte, 0, helloMsgLen),
|
||||
version: ProtocolVersion,
|
||||
suite: CipherSuiteX25519ChaCha20Poly1305,
|
||||
}
|
||||
copy(h.ephPub[:], eph.PublicKey().Bytes())
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h *handshakeState) resetHasherFromTranscript() {
|
||||
h.hasher.Reset()
|
||||
_, _ = h.hasher.Write(h.transcript)
|
||||
}
|
||||
|
||||
func (h *handshakeState) transcriptSum(dst []byte) []byte {
|
||||
return h.hasher.Sum(h.hashBuf[:0])
|
||||
}
|
||||
|
||||
func (h *handshakeState) writeTranscript(p []byte) {
|
||||
h.transcript = append(h.transcript, p...)
|
||||
_, _ = h.hasher.Write(p)
|
||||
}
|
||||
|
||||
func (h *handshakeState) buildClientHello() ([]byte, error) {
|
||||
if h.role != RoleClient {
|
||||
return nil, ErrHandshakeState
|
||||
}
|
||||
msg := h.msgBuf[:0]
|
||||
msg = append(msg, msgClientHello)
|
||||
msg = binary.BigEndian.AppendUint16(msg, h.version)
|
||||
msg = binary.BigEndian.AppendUint16(msg, h.suite)
|
||||
msg = append(msg, h.ephPub[:]...)
|
||||
msg = append(msg, h.identity.Public...)
|
||||
|
||||
baseLen := len(msg)
|
||||
h.writeTranscript(msg)
|
||||
sum := h.transcriptSum(nil)
|
||||
sig := h.identity.Sign(sum)
|
||||
msg = append(msg, sig...)
|
||||
h.writeTranscript(sig)
|
||||
h.msgBuf = msg
|
||||
out := make([]byte, len(msg))
|
||||
copy(out, msg)
|
||||
_ = baseLen
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *handshakeState) processClientHello(msg []byte) error {
|
||||
if h.role != RoleServer {
|
||||
return ErrHandshakeState
|
||||
}
|
||||
if len(msg) < helloMsgLen {
|
||||
return ErrShortMessage
|
||||
}
|
||||
if msg[0] != msgClientHello {
|
||||
return ErrHandshakeState
|
||||
}
|
||||
ver := binary.BigEndian.Uint16(msg[1:3])
|
||||
suite := binary.BigEndian.Uint16(msg[3:5])
|
||||
if ver != ProtocolVersion {
|
||||
return ErrBadVersion
|
||||
}
|
||||
if suite != CipherSuiteX25519ChaCha20Poly1305 {
|
||||
return ErrBadCipher
|
||||
}
|
||||
off := 5
|
||||
peerEph := msg[off : off+ephPubLen]
|
||||
off += ephPubLen
|
||||
peerID := msg[off : off+idPubLen]
|
||||
off += idPubLen
|
||||
sig := msg[off : off+sigLen]
|
||||
|
||||
if h.acceptPeer != nil && !h.acceptPeer(peerID) {
|
||||
return ErrIdentityRejected
|
||||
}
|
||||
|
||||
base := msg[:5+ephPubLen+idPubLen]
|
||||
h.hasher.Reset()
|
||||
_, _ = h.hasher.Write(base)
|
||||
sum := h.transcriptSum(nil)
|
||||
if !VerifyPeer(peerID, sum, sig) {
|
||||
h.hasher.Reset()
|
||||
return ErrBadSignature
|
||||
}
|
||||
|
||||
h.version = ver
|
||||
h.suite = suite
|
||||
copy(h.peerEphPub[:], peerEph)
|
||||
copy(h.peerIDPub[:], peerID)
|
||||
h.hasPeer = true
|
||||
|
||||
h.transcript = append(h.transcript[:0], base...)
|
||||
h.transcript = append(h.transcript, sig...)
|
||||
_, _ = h.hasher.Write(sig)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handshakeState) buildServerHello() ([]byte, error) {
|
||||
if h.role != RoleServer {
|
||||
return nil, ErrHandshakeState
|
||||
}
|
||||
msg := h.msgBuf[:0]
|
||||
msg = append(msg, msgServerHello)
|
||||
msg = binary.BigEndian.AppendUint16(msg, h.version)
|
||||
msg = binary.BigEndian.AppendUint16(msg, h.suite)
|
||||
msg = append(msg, h.ephPub[:]...)
|
||||
msg = append(msg, h.identity.Public...)
|
||||
|
||||
h.writeTranscript(msg)
|
||||
sum := h.transcriptSum(nil)
|
||||
sig := h.identity.Sign(sum)
|
||||
msg = append(msg, sig...)
|
||||
h.writeTranscript(sig)
|
||||
h.msgBuf = msg
|
||||
|
||||
out := make([]byte, len(msg))
|
||||
copy(out, msg)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *handshakeState) processServerHello(msg []byte) error {
|
||||
if h.role != RoleClient {
|
||||
return ErrHandshakeState
|
||||
}
|
||||
if len(msg) < helloMsgLen {
|
||||
return ErrShortMessage
|
||||
}
|
||||
if msg[0] != msgServerHello {
|
||||
return ErrHandshakeState
|
||||
}
|
||||
ver := binary.BigEndian.Uint16(msg[1:3])
|
||||
suite := binary.BigEndian.Uint16(msg[3:5])
|
||||
if ver != ProtocolVersion {
|
||||
return ErrBadVersion
|
||||
}
|
||||
if suite != CipherSuiteX25519ChaCha20Poly1305 {
|
||||
return ErrBadCipher
|
||||
}
|
||||
off := 5
|
||||
peerEph := msg[off : off+ephPubLen]
|
||||
off += ephPubLen
|
||||
peerID := msg[off : off+idPubLen]
|
||||
off += idPubLen
|
||||
sig := msg[off : off+sigLen]
|
||||
|
||||
if h.acceptPeer != nil && !h.acceptPeer(peerID) {
|
||||
return ErrIdentityRejected
|
||||
}
|
||||
|
||||
base := msg[:5+ephPubLen+idPubLen]
|
||||
tLen := len(h.transcript)
|
||||
h.writeTranscript(base)
|
||||
sum := h.transcriptSum(nil)
|
||||
if !VerifyPeer(peerID, sum, sig) {
|
||||
h.transcript = h.transcript[:tLen]
|
||||
h.resetHasherFromTranscript()
|
||||
return ErrBadSignature
|
||||
}
|
||||
|
||||
copy(h.peerEphPub[:], peerEph)
|
||||
copy(h.peerIDPub[:], peerID)
|
||||
h.hasPeer = true
|
||||
h.writeTranscript(sig)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handshakeState) buildFinished(msgType byte) ([]byte, error) {
|
||||
sum := h.transcriptSum(nil)
|
||||
h.labelBuf[0] = msgType
|
||||
copy(h.labelBuf[1:], sum)
|
||||
sig := h.identity.Sign(h.labelBuf[:])
|
||||
msg := h.msgBuf[:0]
|
||||
msg = append(msg, msgType)
|
||||
msg = append(msg, sig...)
|
||||
h.writeTranscript(msg)
|
||||
h.msgBuf = msg
|
||||
|
||||
out := make([]byte, len(msg))
|
||||
copy(out, msg)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (h *handshakeState) processFinished(msg []byte, expectedType byte, peerID []byte) error {
|
||||
if len(msg) < finishedMsgLen {
|
||||
return ErrShortMessage
|
||||
}
|
||||
if msg[0] != expectedType {
|
||||
return ErrHandshakeState
|
||||
}
|
||||
sig := msg[1 : 1+sigLen]
|
||||
sum := h.transcriptSum(nil)
|
||||
h.labelBuf[0] = expectedType
|
||||
copy(h.labelBuf[1:], sum)
|
||||
if !VerifyPeer(peerID, h.labelBuf[:], sig) {
|
||||
return ErrBadSignature
|
||||
}
|
||||
h.writeTranscript(msg[:finishedMsgLen])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handshakeState) deriveKeys() (*trafficKeys, error) {
|
||||
curve := ecdh.X25519()
|
||||
peerPub, err := curve.NewPublicKey(h.peerEphPub[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
shared, err := h.ephPriv.ECDH(peerPub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer zero(shared)
|
||||
sum := h.transcriptSum(nil)
|
||||
return deriveTrafficKeys(shared, sum, h.version, h.suite)
|
||||
}
|
||||
|
||||
func (h *handshakeState) peerIDSlice() []byte {
|
||||
if !h.hasPeer {
|
||||
return nil
|
||||
}
|
||||
return h.peerIDPub[:]
|
||||
}
|
||||
Reference in New Issue
Block a user