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 }