Initial commit
This commit is contained in:
335
session.go
Normal file
335
session.go
Normal file
@@ -0,0 +1,335 @@
|
||||
package lafsession
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Identity *Identity
|
||||
|
||||
Role HandshakeRole
|
||||
|
||||
AcceptPeer func(peerPub []byte) bool
|
||||
|
||||
ReplayWindowSize uint64
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
mu sync.Mutex
|
||||
|
||||
cfg Config
|
||||
hs *handshakeState
|
||||
|
||||
keys *trafficKeys
|
||||
sendAEAD cipher.AEAD
|
||||
recvAEAD cipher.AEAD
|
||||
|
||||
sendSeq uint64
|
||||
recvWin *replayWindow
|
||||
|
||||
sealBuf []byte
|
||||
openBuf []byte
|
||||
nonce [nonceLen]byte
|
||||
aad [recordHeaderLen]byte
|
||||
|
||||
established bool
|
||||
closed bool
|
||||
pendingServerFinished bool
|
||||
}
|
||||
|
||||
func New(cfg Config) (*Session, error) {
|
||||
if cfg.Identity == nil {
|
||||
return nil, ErrIdentityRejected
|
||||
}
|
||||
if cfg.ReplayWindowSize == 0 {
|
||||
cfg.ReplayWindowSize = 1024
|
||||
}
|
||||
hs, err := newHandshake(cfg.Role, cfg.Identity, cfg.AcceptPeer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Session{
|
||||
cfg: cfg,
|
||||
hs: hs,
|
||||
recvWin: newReplayWindow(cfg.ReplayWindowSize),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Session) StartHandshake() ([]byte, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.closed {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
if s.established {
|
||||
return nil, ErrAlreadyEstablished
|
||||
}
|
||||
if s.cfg.Role != RoleClient {
|
||||
return nil, ErrHandshakeState
|
||||
}
|
||||
return s.hs.buildClientHello()
|
||||
}
|
||||
|
||||
func (s *Session) HandleHandshake(msg []byte) (response []byte, err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.closed {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
if s.established {
|
||||
return nil, ErrAlreadyEstablished
|
||||
}
|
||||
if len(msg) < 1 {
|
||||
return nil, ErrShortMessage
|
||||
}
|
||||
|
||||
switch s.cfg.Role {
|
||||
case RoleServer:
|
||||
return s.handleServer(msg)
|
||||
case RoleClient:
|
||||
return s.handleClient(msg)
|
||||
default:
|
||||
return nil, ErrHandshakeState
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) handleServer(msg []byte) ([]byte, error) {
|
||||
switch msg[0] {
|
||||
case msgClientHello:
|
||||
if err := s.hs.processClientHello(msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.hs.buildServerHello()
|
||||
case msgClientFinished:
|
||||
if err := s.hs.processFinished(msg, msgClientFinished, s.hs.peerIDSlice()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys, err := s.hs.deriveKeys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.installKeys(keys); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.pendingServerFinished = true
|
||||
s.established = true
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, ErrHandshakeState
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) handleClient(msg []byte) ([]byte, error) {
|
||||
switch msg[0] {
|
||||
case msgServerHello:
|
||||
if err := s.hs.processServerHello(msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cf, err := s.hs.buildFinished(msgClientFinished)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys, err := s.hs.deriveKeys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.installKeys(keys); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cf, nil
|
||||
case msgServerFinished:
|
||||
if err := s.hs.processFinished(msg, msgServerFinished, s.hs.peerIDSlice()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.keys == nil {
|
||||
return nil, ErrHandshakeState
|
||||
}
|
||||
s.established = true
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, ErrHandshakeState
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) SendFinished() ([]byte, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.closed {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
if s.cfg.Role != RoleServer || !s.pendingServerFinished {
|
||||
return nil, ErrHandshakeState
|
||||
}
|
||||
msg, err := s.hs.buildFinished(msgServerFinished)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.pendingServerFinished = false
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (s *Session) installKeys(keys *trafficKeys) error {
|
||||
var sendKey, recvKey []byte
|
||||
if s.cfg.Role == RoleClient {
|
||||
sendKey, recvKey = keys.c2s[:], keys.s2c[:]
|
||||
} else {
|
||||
sendKey, recvKey = keys.s2c[:], keys.c2s[:]
|
||||
}
|
||||
sa, err := newAEAD(sendKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ra, err := newAEAD(recvKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.keys != nil {
|
||||
s.keys.wipe()
|
||||
}
|
||||
s.keys = keys
|
||||
s.sendAEAD = sa
|
||||
s.recvAEAD = ra
|
||||
s.sendSeq = 0
|
||||
s.recvWin = newReplayWindow(s.cfg.ReplayWindowSize)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Session) Established() bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.established && !s.closed
|
||||
}
|
||||
|
||||
func (s *Session) PeerPublicKey() []byte {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.hs == nil || !s.hs.hasPeer {
|
||||
return nil
|
||||
}
|
||||
out := make([]byte, idPubLen)
|
||||
copy(out, s.hs.peerIDPub[:])
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *Session) Seal(lafPayload []byte) ([]byte, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.closed {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
if !s.established || s.sendAEAD == nil {
|
||||
return nil, ErrNotEstablished
|
||||
}
|
||||
seq := s.sendSeq
|
||||
s.sendSeq++
|
||||
s.sealBuf = sealRecord(s.sendAEAD, seq, recordTypeData, lafPayload, s.sealBuf, &s.nonce, &s.aad)
|
||||
return s.sealBuf, nil
|
||||
}
|
||||
|
||||
func (s *Session) SealTo(dst, lafPayload []byte) ([]byte, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.closed {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
if !s.established || s.sendAEAD == nil {
|
||||
return nil, ErrNotEstablished
|
||||
}
|
||||
seq := s.sendSeq
|
||||
s.sendSeq++
|
||||
return sealRecord(s.sendAEAD, seq, recordTypeData, lafPayload, dst, &s.nonce, &s.aad), nil
|
||||
}
|
||||
|
||||
func (s *Session) Open(frame []byte) ([]byte, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.closed {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
if !s.established || s.recvAEAD == nil {
|
||||
return nil, ErrNotEstablished
|
||||
}
|
||||
if len(frame) < recordHeaderLen+tagLen {
|
||||
return nil, ErrShortMessage
|
||||
}
|
||||
seq := binary.BigEndian.Uint64(frame[1:9])
|
||||
if !s.recvWin.accept(seq) {
|
||||
return nil, ErrReplay
|
||||
}
|
||||
recType, pt, err := openRecord(s.recvAEAD, frame, s.openBuf, &s.nonce, &s.aad)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.openBuf = pt
|
||||
switch recType {
|
||||
case recordTypeData:
|
||||
return pt, nil
|
||||
case recordTypeClose:
|
||||
s.closed = true
|
||||
s.wipeKeys()
|
||||
return nil, ErrClosed
|
||||
default:
|
||||
return nil, ErrBadTag
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) OpenTo(dst, frame []byte) ([]byte, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.closed {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
if !s.established || s.recvAEAD == nil {
|
||||
return nil, ErrNotEstablished
|
||||
}
|
||||
if len(frame) < recordHeaderLen+tagLen {
|
||||
return nil, ErrShortMessage
|
||||
}
|
||||
seq := binary.BigEndian.Uint64(frame[1:9])
|
||||
if !s.recvWin.accept(seq) {
|
||||
return nil, ErrReplay
|
||||
}
|
||||
recType, pt, err := openRecord(s.recvAEAD, frame, dst, &s.nonce, &s.aad)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch recType {
|
||||
case recordTypeData:
|
||||
return pt, nil
|
||||
case recordTypeClose:
|
||||
s.closed = true
|
||||
s.wipeKeys()
|
||||
return nil, ErrClosed
|
||||
default:
|
||||
return nil, ErrBadTag
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) Close() (frame []byte, err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.closed {
|
||||
return nil, nil
|
||||
}
|
||||
if s.established && s.sendAEAD != nil {
|
||||
seq := s.sendSeq
|
||||
s.sendSeq++
|
||||
s.sealBuf = sealRecord(s.sendAEAD, seq, recordTypeClose, nil, s.sealBuf, &s.nonce, &s.aad)
|
||||
frame = s.sealBuf
|
||||
}
|
||||
s.closed = true
|
||||
s.established = false
|
||||
s.wipeKeys()
|
||||
return frame, err
|
||||
}
|
||||
|
||||
func (s *Session) wipeKeys() {
|
||||
if s.keys != nil {
|
||||
s.keys.wipe()
|
||||
s.keys = nil
|
||||
}
|
||||
s.sendAEAD = nil
|
||||
s.recvAEAD = nil
|
||||
}
|
||||
Reference in New Issue
Block a user