Remove noise.socket, now unused.
[champa.git] / noise / session.go
blob6b80da0ca85bf788a4786f76517912c9661e0e9f
1 /*
2 A Session represents a pair of Noise CipherState objects, one for receiving and
3 one for sending, resulting from a handshake. The Encrypt and Decrypt methods of
4 Session deal in Noise transport messages with prepended 64-bit big-endian
5 explicit nonces.
7 The procedure for getting a Session differs depending on whether you are the
8 initiator or the responder. As an initiator, call InitiateHandshake to get a
9 PreSession and an initiator handshake message to send, then call FinishHandshake
10 with the responder's handshake message to get a Session.
11 pre, initMsg, err := InitiateHandshake(nil, pubkey)
12 // err check
13 // send initMsg to the responder
14 // receive respMsg from the responder
15 session, err := pre.FinishHandshake(respMsg)
16 // err check
17 As a responder, receive the initiator's handshake message and call
18 AcceptHandshake on it to get a Session and a handshake message to send back to
19 the initiator.
20 // receive initMsg from the initiator
21 session, respMsg, err := AcceptHandshake(nil, initMsg, privkey)
22 // err check
23 // send respMsg to the initiator
25 package noise
27 import (
28 "encoding/binary"
29 "errors"
30 "sync"
32 "github.com/flynn/noise"
35 var errPayload = errors.New("unexpected payload in handshake message")
36 var errMissingNonce = errors.New("slice is too short to contain a nonce")
37 var errInvalidNonce = errors.New("nonce is already used or out of window")
39 // PreSession represents a partially initialized Session, from the point of view
40 // of an initiator that has sent its handshake message but has not yet received
41 // the responder's handshake message. Call FinishHandshake with the responder's
42 // handshake message to convert the PreSession into a full Session.
43 type PreSession struct {
44 handshakeState *noise.HandshakeState
47 // Session represents an initialized Noise session, post-handshake with all
48 // necessary key material.
49 type Session struct {
50 recv, send *noise.CipherState
51 recvLock, sendLock sync.Mutex
52 replay replayWindow
55 // InitiateHandshake prepares a PreSession and returns an initiator handshake
56 // message to be sent to the responder. out is a byte slice (may be nil) to
57 // which the handshake message will be appended. pubkey is the responder's
58 // public key.
59 func InitiateHandshake(out, pubkey []byte) (*PreSession, []byte, error) {
60 config := newConfig()
61 config.Initiator = true
62 config.PeerStatic = pubkey
63 handshakeState, err := noise.NewHandshakeState(config)
64 if err != nil {
65 return nil, nil, err
68 // -> e, es
69 out, _, _, err = handshakeState.WriteMessage(out, nil)
70 if err != nil {
71 return nil, nil, err
74 return &PreSession{handshakeState: handshakeState}, out, nil
77 // FinishHandshake completes a handshake with the responder's handshake message
78 // and converts a PreSession into a Session. The PreSession should not be used
79 // after calling this method.
80 func (pre *PreSession) FinishHandshake(msg []byte) (*Session, error) {
81 // <- e, es
82 payload, send, recv, err := pre.handshakeState.ReadMessage(nil, msg)
83 if err != nil {
84 return nil, err
86 if len(payload) != 0 {
87 return nil, errPayload
90 return &Session{recv: recv, send: send}, nil
93 // AcceptHandshake accepts an initiator handshake message, and returns a Session
94 // along with a handshake message to be sent back to the initiator. out is a
95 // byte slice (may be nil) to which the handshake message will be appended.
96 // privkey is the responder's private key.
97 func AcceptHandshake(out, msg, privkey []byte) (*Session, []byte, error) {
98 config := newConfig()
99 config.Initiator = false
100 config.StaticKeypair = noise.DHKey{
101 Private: privkey,
102 Public: PubkeyFromPrivkey(privkey),
104 handshakeState, err := noise.NewHandshakeState(config)
105 if err != nil {
106 return nil, nil, err
109 // -> e, es
110 payload, _, _, err := handshakeState.ReadMessage(nil, msg)
111 if err != nil {
112 return nil, nil, err
114 if len(payload) != 0 {
115 return nil, nil, errPayload
118 // <- e, es
119 out, recv, send, err := handshakeState.WriteMessage(out, nil)
120 if err != nil {
121 return nil, nil, err
124 return &Session{recv: recv, send: send}, out, nil
127 // Encrypt produces an encrypted Noise transport message with an explicit nonce.
128 // It encrypts the plaintext p, prepends the nonce, appends the message to out
129 // and returns out.
130 func (session *Session) Encrypt(out, p []byte) ([]byte, error) {
131 session.sendLock.Lock()
132 defer session.sendLock.Unlock()
134 // Prepend the nonce.
135 var buf [8]byte
136 binary.BigEndian.PutUint64(buf[:], session.send.Nonce())
137 out = append(out, buf[:]...)
139 // Encrypt the message.
140 return session.send.Encrypt(out, nil, p)
143 // Decrypt decrypts a Noise transport message that has an explicit nonce. It
144 // appends the plaintext to out and returns out. It returns a non-nil error when
145 // the message cannot be authenticated or the nonce has already been used or is
146 // out of window.
147 func (session *Session) Decrypt(out, msg []byte) ([]byte, error) {
148 // Decode the prepended nonce.
149 if len(msg) < 8 {
150 return nil, errMissingNonce
152 var nonce uint64 = binary.BigEndian.Uint64(msg[:8])
154 session.recvLock.Lock()
155 defer session.recvLock.Unlock()
157 // Decrypt the message.
158 session.recv.SetNonce(nonce)
159 p, err := session.recv.Decrypt(out, nil, msg[8:])
160 if err != nil {
161 return nil, err
164 // The message was authenticated; is its nonce acceptable (i.e., in a
165 // recent window and not a replay)? It is important to do this check
166 // only after successful decryption+authentication.
167 if !session.replay.CheckAndUpdate(nonce) {
168 return nil, errInvalidNonce
171 return p, nil