forked from coniks-sys/coniks-go
-
Notifications
You must be signed in to change notification settings - Fork 1
/
server.go
329 lines (305 loc) · 9.37 KB
/
server.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
package keyserver
import (
"context"
"crypto/tls"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/BurntSushi/toml"
"github.com/coniks-sys/coniks-go/crypto/sign"
"github.com/coniks-sys/coniks-go/crypto/vrf"
"github.com/coniks-sys/coniks-go/protocol"
"github.com/coniks-sys/coniks-go/utils"
)
// A ServerConfig contains configuration values
// which are read at initialization time from
// a TOML format configuration file.
type ServerConfig struct {
// LoadedHistoryLength is the maximum number of
// snapshots kept in memory.
LoadedHistoryLength uint64 `toml:"loaded_history_length"`
// Policies contains the server's CONIKS policies configuration.
Policies *ServerPolicies `toml:"policies"`
// Addresses contains the server's connections configuration.
Addresses []*Address `toml:"addresses"`
Logger *utils.LoggerConfig `toml:"logger"`
configFilePath string
}
// An Address describes a server's connection.
// It makes the server connections configurable
// so that a key server implementation can easily
// be run by a first-party identity provider or
// a third-party communication service.
// It supports two types of connections: a TCP connection ("tcp")
// and a Unix socket connection ("unix").
//
// Allowing registration has to be specified explicitly for each connection.
// Other types of requests are allowed by default.
// One can think of a registration as a "write" to a key directory,
// while the other request types are "reads".
// So, by default, addresses are "read-only".
//
// Additionally, TCP connections must use TLS for added security,
// and each is required to specify a TLS certificate and corresponding
// private key.
type Address struct {
// Address is formatted as a url: scheme://address.
Address string `toml:"address"`
AllowRegistration bool `toml:"allow_registration,omitempty"`
// TLSCertPath is a path to the server's TLS Certificate,
// which has to be set if the connection is TCP.
TLSCertPath string `toml:"cert,omitempty"`
// TLSKeyPath is a path to the server's TLS private key,
// which has to be set if the connection is TCP.
TLSKeyPath string `toml:"key,omitempty"`
}
// ServerPolicies contains a server's CONIKS policies configuration
// including paths to the VRF private key, the signing private
// key and the epoch deadline value in seconds.
type ServerPolicies struct {
EpochDeadline protocol.Timestamp `toml:"epoch_deadline"`
VRFKeyPath string `toml:"vrf_key_path"`
SignKeyPath string `toml:"sign_key_path"` // it should be a part of policies, see #47
vrfKey vrf.PrivateKey
signKey sign.PrivateKey
}
// A ConiksServer represents a CONIKS key server.
// It wraps a ConiksDirectory with a network layer which
// handles requests/responses and their encoding/decoding.
// A ConiksServer also supports concurrent handling of requests and
// a mechanism to update the underlying ConiksDirectory automatically
// at regular time intervals.
type ConiksServer struct {
logger *utils.Logger
sync.RWMutex
dir *protocol.ConiksDirectory
stop chan struct{}
waitStop sync.WaitGroup
waitCloseConn sync.WaitGroup
configFilePath string
reloadChan chan os.Signal
epochTimer *time.Timer
httpServer *http.Server
}
// LoadServerConfig loads the ServerConfig for the server from the
// corresponding config file. It reads the siging key pair and the VRF key
// pair into the ServerConfig instance and updates the path of
// TLS certificate files of each Address to absolute path.
func LoadServerConfig(file string) (*ServerConfig, error) {
var conf ServerConfig
if _, err := toml.DecodeFile(file, &conf); err != nil {
return nil, fmt.Errorf("Failed to load config: %v", err)
}
// load signing key
signPath := utils.ResolvePath(conf.Policies.SignKeyPath, file)
signKey, err := ioutil.ReadFile(signPath)
if err != nil {
return nil, fmt.Errorf("Cannot read signing key: %v", err)
}
if len(signKey) != sign.PrivateKeySize {
return nil, fmt.Errorf("Signing key must be 64 bytes (got %d)", len(signKey))
}
// load VRF key
vrfPath := utils.ResolvePath(conf.Policies.VRFKeyPath, file)
vrfKey, err := ioutil.ReadFile(vrfPath)
if err != nil {
return nil, fmt.Errorf("Cannot read VRF key: %v", err)
}
if len(vrfKey) != vrf.PrivateKeySize {
return nil, fmt.Errorf("VRF key must be 64 bytes (got %d)", len(vrfKey))
}
conf.configFilePath = file
conf.Policies.vrfKey = vrfKey
conf.Policies.signKey = signKey
// also update path for TLS cert files
for _, addr := range conf.Addresses {
addr.TLSCertPath = utils.ResolvePath(addr.TLSCertPath, file)
addr.TLSKeyPath = utils.ResolvePath(addr.TLSKeyPath, file)
}
// logger config
conf.Logger.Path = utils.ResolvePath(conf.Logger.Path, file)
return &conf, nil
}
// NewConiksServer creates a new reference implementation of
// a CONIKS key server.
func NewConiksServer(conf *ServerConfig) *ConiksServer {
// create server instance
server := new(ConiksServer)
server.logger = utils.NewLogger(conf.Logger)
server.dir = protocol.NewDirectory(
conf.Policies.EpochDeadline,
conf.Policies.vrfKey,
conf.Policies.signKey,
conf.LoadedHistoryLength,
true)
server.stop = make(chan struct{})
server.configFilePath = conf.configFilePath
server.reloadChan = make(chan os.Signal, 1)
signal.Notify(server.reloadChan, syscall.SIGUSR2)
server.epochTimer = time.NewTimer(time.Duration(conf.Policies.EpochDeadline) * time.Second)
return server
}
// Run implements the main functionality of the key server.
// It listens for all declared connections with corresponding
// permissions.
// It also supports hot-reloading the configuration by listening for
// SIGUSR2 signal.
func (server *ConiksServer) Run(addrs []*Address) {
server.waitStop.Add(1)
go func() {
server.epochUpdate()
server.waitStop.Done()
}()
hasRegistrationPerm := false
for i := 0; i < len(addrs); i++ {
addr := addrs[i]
perms := updatePerms(addr)
hasRegistrationPerm = hasRegistrationPerm || addr.AllowRegistration
u, err := url.Parse(addr.Address)
if err != nil {
panic(err)
}
switch u.Scheme {
case "https":
mux := http.NewServeMux()
mux.HandleFunc("/", server.makeHTTPSHandler(perms))
ln, tlsConfig := resolveAndListen(addr)
httpSrv := &http.Server{
Addr: u.Host,
Handler: mux,
TLSConfig: tlsConfig,
}
server.httpServer = httpSrv
go func() {
httpSrv.Serve(ln)
}()
case "tcp", "unix":
ln, tlsConfig := resolveAndListen(addr)
server.waitStop.Add(1)
go func() {
server.handleRequests(ln, tlsConfig, server.makeHandler(perms))
server.waitStop.Done()
}()
}
verb := "Listening"
if addr.AllowRegistration {
verb = "Accepting registrations"
}
server.logger.Info(verb, "address", addr.Address)
}
if !hasRegistrationPerm {
server.logger.Warn("None of the addresses permit registration")
}
server.waitStop.Add(1)
go func() {
server.updatePolicies()
server.waitStop.Done()
}()
}
func (server *ConiksServer) epochUpdate() {
for {
select {
case <-server.stop:
return
case <-server.epochTimer.C:
server.Lock()
server.dir.Update()
server.epochTimer.Reset(time.Duration(server.dir.EpochDeadline()) * time.Second)
server.Unlock()
}
}
}
func (server *ConiksServer) updatePolicies() {
for {
select {
case <-server.stop:
return
case <-server.reloadChan:
// read server policies from config file
conf, err := LoadServerConfig(server.configFilePath)
if err != nil {
// error occured while reading server config
// simply abort the reloading policies process
server.logger.Error(err.Error())
return
}
server.Lock()
server.dir.SetPolicies(conf.Policies.EpochDeadline)
server.Unlock()
server.logger.Info("Policies reloaded!")
}
}
}
func resolveAndListen(addr *Address) (ln net.Listener, tlsConfig *tls.Config) {
u, err := url.Parse(addr.Address)
if err != nil {
panic(err)
}
switch u.Scheme {
case "https":
cer, err := tls.LoadX509KeyPair(addr.TLSCertPath, addr.TLSKeyPath)
if err != nil {
panic(err)
}
tlsConfig = &tls.Config{Certificates: []tls.Certificate{cer}}
ln, err = tls.Listen("tcp", u.Host, tlsConfig)
if err != nil {
panic(err)
}
return
case "tcp":
// force to use TLS
cer, err := tls.LoadX509KeyPair(addr.TLSCertPath, addr.TLSKeyPath)
if err != nil {
panic(err)
}
tlsConfig = &tls.Config{Certificates: []tls.Certificate{cer}}
tcpaddr, err := net.ResolveTCPAddr(u.Scheme, u.Host)
if err != nil {
panic(err)
}
ln, err = net.ListenTCP(u.Scheme, tcpaddr)
if err != nil {
panic(err)
}
return
case "unix":
unixaddr, err := net.ResolveUnixAddr(u.Scheme, u.Path)
if err != nil {
panic(err)
}
ln, err = net.ListenUnix(u.Scheme, unixaddr)
if err != nil {
panic(err)
}
return
default:
panic("Unknown network type")
}
}
func updatePerms(addr *Address) map[int]bool {
perms := make(map[int]bool)
perms[protocol.KeyLookupType] = true
perms[protocol.KeyLookupInEpochType] = true
perms[protocol.MonitoringType] = true
perms[protocol.RegistrationType] = addr.AllowRegistration
return perms
}
// Shutdown closes all of the server's connections and shuts down the server.
func (server *ConiksServer) Shutdown() error {
close(server.stop)
if server.httpServer != nil {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
server.httpServer.Shutdown(ctx)
}
server.waitStop.Wait()
return nil
}