-
Notifications
You must be signed in to change notification settings - Fork 0
/
session.go
171 lines (147 loc) · 4.4 KB
/
session.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package gothemis
import (
"encoding/binary"
"errors"
)
type SecureSession interface {
ConnectRequest() ([]byte, error)
}
type PublicKey []byte
type ProtocolEvent int
type Callback interface {
Write(date []byte) (int, error)
Read(data []byte) (int, error)
ProtocolStateChanged(event ProtocolEvent)
GetPublicKeyForId(id []byte) (PublicKey, error)
}
var (
ErrEmptyCallback = errors.New("secure session callback is nil")
ErrEmptyPrivateKey = errors.New("empty private key")
)
const (
// id tag
THEMIS_SESSION_ID_TAG = "TSID"
// protocol tag
THEMIS_SESSION_PROTO_TAG = "TSPM"
)
func validateCallback(c Callback) error {
if c == nil {
return ErrEmptyCallback
}
return nil
}
type secureSession struct {
id []byte
ecdhKeypair *KeyPair
signKey *PrivateECKey
peerPublicKey *PublicECKey
callback Callback
}
func newSecureSession(id []byte, signKey *PrivateECKey, publicKey *PublicECKey, callback Callback) (*secureSession, error) {
if signKey == nil {
return nil, ErrEmptyPrivateKey
}
if err := validateCallback(callback); err != nil {
return nil, err
}
kp, err := NewECKeyPair()
if err != nil {
return nil, err
}
return &secureSession{id: id, ecdhKeypair: kp, signKey: signKey, callback: callback, peerPublicKey: publicKey}, nil
}
const (
soterTagLength = 4
containerLength = soterTagLength + 4 + 4 // tag[soterTagLength] + size[uint32] + crc[uint32]
)
var ErrInvalidSoterContainerLength = errors.New("small slice for soter container")
var ErrInvalidBufferForCRC = errors.New("buffer has incorrect size according to soter header")
// soterContainer has next structure:
// {
// tag [soterTagLength]byte
// size uint32
// crc uint32
// }
type soterContainer []byte
func (container soterContainer) calculateCRC(buf []byte) error {
if len(container) < containerLength {
return ErrInvalidSoterContainerLength
}
size := binary.BigEndian.Uint32(container[4:8])
if len(buf) < int(size){
return ErrInvalidBufferForCRC
}
crcHash := NewCRC32()
crcHash.Write(buf[:size])
binary.LittleEndian.PutUint32(container[8:12], crcHash.Sum32())
return nil
}
func (container soterContainer) setSizeToContainer(size int) error {
if len(container) < containerLength {
return ErrInvalidSoterContainerLength
}
binary.BigEndian.PutUint32(container[4:8], uint32(size+containerLength))
return nil
}
func (container soterContainer) setTag(tag []byte) error {
if len(container) < containerLength {
return ErrInvalidSoterContainerLength
}
copy(container[:4], tag)
return nil
}
func (session *secureSession) ConnectRequest() ([]byte, error) {
pubkeyBytes, err := session.peerPublicKey.Marshal()
if err != nil {
return nil, err
}
signature, err := signECDSA(pubkeyBytes, session.signKey.private)
if err != nil {
return nil, err
}
//length := 2*containerLength + len(session.id)
totalLength := 2*containerLength + len(session.id) + len(pubkeyBytes) + len(signature)
output := make([]byte, totalLength)
// save total metadata and first soter container header
container := soterContainer(output[:containerLength])
if err := container.setTag([]byte(THEMIS_SESSION_PROTO_TAG)); err != nil {
return nil, err
}
if err := container.setSizeToContainer(totalLength - containerLength); err != nil {
return nil, err
}
// save id length as second soter container
container = soterContainer(output[containerLength:2*containerLength])
if err := container.setTag([]byte(THEMIS_SESSION_ID_TAG)); err != nil {
return nil, err
}
if err := container.setSizeToContainer(len(session.id)); err != nil {
return nil, err
}
// save id after two containers
index := 2 * containerLength
copy(output[index:index+len(session.id)], session.id)
// calculate crc for header related with id
if err := container.calculateCRC(output[containerLength:]); err != nil {
return nil, err
}
// save public key after id
index += len(session.id)
copy(output[index:index+len(pubkeyBytes)], pubkeyBytes)
// save signature of public key after public key
index += len(pubkeyBytes)
copy(output[index:], signature)
// calculate total crc
container = soterContainer(output[:containerLength])
if err := container.calculateCRC(output); err != nil {
return nil, err
}
return output, nil
}
func NewSecureSession(id []byte, signatureKey *PrivateECKey, publicKey *PublicECKey, callback Callback) (SecureSession, error) {
s, err := newSecureSession(id, signatureKey, publicKey, callback)
if err != nil {
return nil, err
}
return s, nil
}