conf.go 15.9 KB
Newer Older
Sietse Ringers's avatar
Sietse Ringers committed
1
package requestorserver
2
3
4

import (
	"crypto/rsa"
Sietse Ringers's avatar
Sietse Ringers committed
5
	"crypto/tls"
6
	"encoding/json"
7
	"fmt"
8
9
	"regexp"
	"strconv"
10
11
12
	"strings"

	"github.com/dgrijalva/jwt-go"
13
	"github.com/go-errors/errors"
14
15
16
17
18
19
	"github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/fs"
	"github.com/privacybydesign/irmago/server"
)

type Configuration struct {
20
	*server.Configuration `mapstructure:",squash"`
21

22
23
24
	// Disclosing, signing or issuance permissions that apply to all requestors
	Permissions `mapstructure:",squash"`

Sietse Ringers's avatar
Sietse Ringers committed
25
26
27
	// Whether or not incoming session requests should be authenticated. If false, anyone
	// can submit session requests. If true, the request is first authenticated against the
	// server configuration before the server accepts it.
28
	DisableRequestorAuthentication bool `json:"no_auth" mapstructure:"no_auth"`
29

30
	// Address to listen at
31
	ListenAddress string `json:"listen_addr" mapstructure:"listen_addr"`
32
33
	// Port to listen at
	Port int `json:"port" mapstructure:"port"`
Sietse Ringers's avatar
Sietse Ringers committed
34
	// TLS configuration
35
36
37
38
	TlsCertificate     string `json:"tls_cert" mapstructure:"tls_cert"`
	TlsCertificateFile string `json:"tls_cert_file" mapstructure:"tls_cert_file"`
	TlsPrivateKey      string `json:"tls_privkey" mapstructure:"tls_privkey"`
	TlsPrivateKeyFile  string `json:"tls_privkey_file" mapstructure:"tls_privkey_file"`
39

40
	// If specified, start a separate server for the IRMA app at his port
41
	ClientPort int `json:"client_port" mapstructure:"client_port"`
42
	// If clientport is specified, the server for the IRMA app listens at this address
43
	ClientListenAddress string `json:"client_listen_addr" mapstructure:"client_listen_addr"`
Sietse Ringers's avatar
Sietse Ringers committed
44
	// TLS configuration for irmaclient HTTP API
45
46
47
48
	ClientTlsCertificate     string `json:"client_tls_cert" mapstructure:"client_tls_cert"`
	ClientTlsCertificateFile string `json:"client_tls_cert_file" mapstructure:"client_tls_cert_file"`
	ClientTlsPrivateKey      string `json:"client_tls_privkey" mapstructure:"client_tls_privkey"`
	ClientTlsPrivateKeyFile  string `json:"client_tls_privkey_file" mapstructure:"client_tls_privkey_file"`
49

50
51
52
	// Requestor-specific permission and authentication configuration
	RequestorsString string               `json:"-" mapstructure:"requestors"`
	Requestors       map[string]Requestor `json:"requestors"`
53

54
	// Used in the "iss" field of result JWTs from /result-jwt and /getproof
55
	JwtIssuer string `json:"jwt_issuer" mapstructure:"jwt_issuer"`
56

57
	// Private key to sign result JWTs with. If absent, /result-jwt and /getproof are disabled.
58
59
	JwtPrivateKey     string `json:"jwt_privkey" mapstructure:"jwt_privkey"`
	JwtPrivateKeyFile string `json:"jwt_privkey_file" mapstructure:"jwt_privkey_file"`
60

61
	// Max age in seconds of a session request JWT (using iat field)
62
	MaxRequestAge int `json:"max_request_age" mapstructure:"max_request_age"`
63

64
65
66
67
68
	// Host files under this path as static files (leave empty to disable)
	StaticPath string `json:"static_path" mapstructure:"static_path"`
	// Host static files under this URL prefix
	StaticPrefix string `json:"static_prefix" mapstructure:"static_prefix"`

69
70
71
72
	StaticSessions map[string]interface{} `json:"static_sessions"`

	staticSessions map[string]irma.RequestorRequest
	jwtPrivateKey  *rsa.PrivateKey
73
74
}

Sietse Ringers's avatar
Sietse Ringers committed
75
// Permissions specify which attributes or credential a requestor may verify or issue.
76
type Permissions struct {
77
78
79
	Disclosing []string `json:"disclose_perms" mapstructure:"disclose_perms"`
	Signing    []string `json:"sign_perms" mapstructure:"sign_perms"`
	Issuing    []string `json:"issue_perms" mapstructure:"issue_perms"`
80
81
}

Sietse Ringers's avatar
Sietse Ringers committed
82
83
// Requestor contains all configuration (disclosure or verification permissions and authentication)
// for a requestor.
84
type Requestor struct {
85
	Permissions `mapstructure:",squash"`
86

87
	AuthenticationMethod  AuthenticationMethod `json:"auth_method" mapstructure:"auth_method"`
88
	AuthenticationKey     string               `json:"key" mapstructure:"key"`
89
	AuthenticationKeyFile string               `json:"key_file" mapstructure:"key_file"`
90
91
}

Sietse Ringers's avatar
Sietse Ringers committed
92
93
94
95
// CanIssue returns whether or not the specified requestor may issue the specified credentials.
// (In case of combined issuance/disclosure sessions, this method does not check whether or not
// the identity provider is allowed to verify the attributes being verified; use CanVerifyOrSign
// for that).
96
func (conf *Configuration) CanIssue(requestor string, creds []*irma.CredentialRequest) (bool, string) {
97
	permissions := append(conf.Requestors[requestor].Issuing, conf.Issuing...)
Sietse Ringers's avatar
Sietse Ringers committed
98
99
100
	if len(permissions) == 0 { // requestor is not present in the permissions
		return false, ""
	}
101
102
103
104
105
106
107
108
109
110
111
112
113
114

	for _, cred := range creds {
		id := cred.CredentialTypeID
		if contains(permissions, "*") ||
			contains(permissions, id.Root()+".*") ||
			contains(permissions, id.IssuerIdentifier().String()+".*") ||
			contains(permissions, id.String()) {
			continue
		} else {
			return false, id.String()
		}
	}

	return true, ""
115
116
}

Sietse Ringers's avatar
Sietse Ringers committed
117
118
// CanVerifyOrSign returns whether or not the specified requestor may use the selected attributes
// in any of the supported session types.
119
func (conf *Configuration) CanVerifyOrSign(requestor string, action irma.Action, disjunctions irma.AttributeConDisCon) (bool, string) {
120
121
122
	var permissions []string
	switch action {
	case irma.ActionDisclosing:
123
		permissions = append(conf.Requestors[requestor].Disclosing, conf.Disclosing...)
124
	case irma.ActionIssuing:
125
		permissions = append(conf.Requestors[requestor].Disclosing, conf.Disclosing...)
126
	case irma.ActionSigning:
127
		permissions = append(conf.Requestors[requestor].Signing, conf.Signing...)
128
129
130
131
132
	}
	if len(permissions) == 0 { // requestor is not present in the permissions
		return false, ""
	}

133
134
135
136
137
138
139
140
141
	err := disjunctions.Iterate(func(attr *irma.AttributeRequest) error {
		if contains(permissions, "*") ||
			contains(permissions, attr.Type.Root()+".*") ||
			contains(permissions, attr.Type.CredentialTypeIdentifier().IssuerIdentifier().String()+".*") ||
			contains(permissions, attr.Type.CredentialTypeIdentifier().String()+".*") ||
			contains(permissions, attr.Type.String()) {
			return nil
		} else {
			return errors.New(attr.Type.String())
142
		}
143
144
145
	})
	if err != nil {
		return false, err.Error()
146
147
148
149
150
151
152
153
154
	}
	return true, ""
}

func (conf *Configuration) initialize() error {
	if err := conf.readPrivateKey(); err != nil {
		return err
	}

155
	if conf.DisableRequestorAuthentication {
156
		authenticators = map[AuthenticationMethod]Authenticator{AuthenticationMethodNone: NilAuthenticator{}}
157
158
159
160
161
162
163
164
165
166
167
168
		conf.Logger.Warn("Authentication of incoming session requests disabled: anyone who can reach this server can use it")
		havekeys, err := conf.HavePrivateKeys()
		if err != nil {
			return err
		}
		if len(conf.Permissions.Issuing) > 0 && havekeys {
			if conf.separateClientServer() || !conf.Production {
				conf.Logger.Warn("Issuance enabled and private keys installed: anyone who can reach this server can use it to issue attributes")
			} else {
				return errors.New("If issuing is enabled in production mode, requestor authentication must be enabled, or client_listen_addr and client_port must be used")
			}
		}
169
	} else {
170
171
172
		if len(conf.Requestors) == 0 {
			return errors.New("No requestors configured; either configure one or more requestors or disable requestor authentication")
		}
173
		authenticators = map[AuthenticationMethod]Authenticator{
174
175
			AuthenticationMethodHmac:      &HmacAuthenticator{hmackeys: map[string]interface{}{}, maxRequestAge: conf.MaxRequestAge},
			AuthenticationMethodPublicKey: &PublicKeyAuthenticator{publickeys: map[string]interface{}{}, maxRequestAge: conf.MaxRequestAge},
176
177
			AuthenticationMethodToken:     &PresharedKeyAuthenticator{presharedkeys: map[string]string{}},
		}
178

179
180
181
182
		// Initialize authenticators
		for name, requestor := range conf.Requestors {
			authenticator, ok := authenticators[requestor.AuthenticationMethod]
			if !ok {
183
184
				return errors.Errorf("Requestor %s has unsupported authentication type %s (supported methods: %s, %s, %s)",
					name, requestor.AuthenticationMethod, AuthenticationMethodToken, AuthenticationMethodHmac, AuthenticationMethodPublicKey)
185
186
187
188
189
			}
			if err := authenticator.Initialize(name, requestor); err != nil {
				return err
			}
		}
190
191
	}

192
193
194
195
	if conf.Port <= 0 || conf.Port > 65535 {
		return errors.Errorf("Port must be between 1 and 65535 (was %d)", conf.Port)
	}

196
	if conf.ClientPort != 0 && conf.ClientPort == conf.Port {
197
		return errors.New("If client_port is given it must be different from port")
198
199
	}
	if conf.ClientPort < 0 || conf.ClientPort > 65535 {
200
		return errors.Errorf("client_port must be between 0 and 65535 (was %d)", conf.ClientPort)
201
202
	}
	if conf.ClientListenAddress != "" && conf.ClientPort == 0 {
203
		return errors.New("client_listen_addr must be combined with a nonzero client_port")
204
205
	}

Sietse Ringers's avatar
Sietse Ringers committed
206
207
208
209
210
211
212
213
214
	tlsConf, err := conf.tlsConfig()
	if err != nil {
		return errors.WrapPrefix(err, "Failed to read TLS configuration", 0)
	}
	clientTlsConf, err := conf.clientTlsConfig()
	if err != nil {
		return errors.WrapPrefix(err, "Failed to read client TLS configuration", 0)
	}

215
216
217
218
	if err := conf.validatePermissions(); err != nil {
		return err
	}

219
220
221
222
223
224
225
226
227
228
229
230
	if conf.StaticPath != "" {
		if err := fs.AssertPathExists(conf.StaticPath); err != nil {
			return errors.WrapPrefix(err, "Invalid static_path", 0)
		}
		if conf.StaticPrefix[0] != '/' {
			return errors.New("static_prefix must start with a slash, was " + conf.StaticPrefix)
		}
		if len(conf.StaticPrefix) > 1 && !strings.HasSuffix(conf.StaticPrefix, "/") {
			conf.StaticPrefix = conf.StaticPrefix + "/"
		}
	}

231
232
233
	if conf.URL != "" {
		if !strings.HasSuffix(conf.URL, "/") {
			conf.URL = conf.URL + "/"
234
		}
235
236
237
		if !strings.HasSuffix(conf.URL, "irma/") {
			conf.URL = conf.URL + "irma/"
		}
238
		// replace "port" in url with actual port
239
240
241
242
243
		port := conf.ClientPort
		if port == 0 {
			port = conf.Port
		}
		replace := "$1:" + strconv.Itoa(port)
244
		conf.URL = string(regexp.MustCompile("(https?://[^/]*):port").ReplaceAll([]byte(conf.URL), []byte(replace)))
245

Sietse Ringers's avatar
Sietse Ringers committed
246
247
248
249
250
251
		separateClientServer := conf.separateClientServer()
		if (separateClientServer && clientTlsConf != nil) || (!separateClientServer && tlsConf != nil) {
			if strings.HasPrefix(conf.URL, "http://") {
				conf.URL = "https://" + conf.URL[len("http://"):]
			}
		}
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
	if len(conf.StaticSessions) != 0 && conf.jwtPrivateKey == nil {
		conf.Logger.Warn("Static sessions enabled and no JWT private key installed. Ensure that POSTs to the callback URLs of static sessions are trustworthy by keeping the callback URLs secret and by using HTTPS.")
	}
	conf.staticSessions = make(map[string]irma.RequestorRequest)
	for name, r := range conf.StaticSessions {
		if !regexp.MustCompile("^[a-zA-Z0-9_]+$").MatchString(name) {
			return errors.Errorf("static session name %s not allowed, must be alphanumeric", name)
		}
		j, err := json.Marshal(r)
		if err != nil {
			return errors.WrapPrefix(err, "failed to parse static session request "+name, 0)
		}
		rrequest, err := server.ParseSessionRequest(j)
		if err != nil {
			return errors.WrapPrefix(err, "failed to parse static session request "+name, 0)
		}
		action := rrequest.SessionRequest().Action()
		if action != irma.ActionDisclosing && action != irma.ActionSigning {
			return errors.Errorf("static session %s must be either a disclosing or signing session", name)
		}
		if rrequest.Base().CallbackUrl == "" {
			return errors.Errorf("static session %s has no callback URL", name)
		}
		conf.staticSessions[name] = rrequest
	}

280
281
282
	return nil
}

283
284
285
286
287
func (conf *Configuration) validatePermissions() error {
	if conf.DisableRequestorAuthentication && len(conf.Requestors) != 0 {
		return errors.New("Requestors must not be configured when requestor authentication is disabled")
	}

288
	errs := conf.validatePermissionSet("Global", conf.Permissions)
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
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
	for name, requestor := range conf.Requestors {
		errs = append(errs, conf.validatePermissionSet("Requestor "+name, requestor.Permissions)...)
	}
	if len(errs) != 0 {
		return errors.New("Errors encountered in permissions:\n" + strings.Join(errs, "\n"))
	}
	return nil
}

func (conf *Configuration) validatePermissionSet(requestor string, requestorperms Permissions) []string {
	var errs []string
	perms := map[string][]string{
		"issuing":    requestorperms.Issuing,
		"signing":    requestorperms.Signing,
		"disclosing": requestorperms.Disclosing,
	}
	permissionlength := map[string]int{"issuing": 3, "signing": 4, "disclosing": 4}

	for typ, typeperms := range perms {
		for _, permission := range typeperms {
			parts := strings.Split(permission, ".")
			if parts[len(parts)-1] == "*" {
				if len(parts) > permissionlength[typ] {
					errs = append(errs, fmt.Sprintf("%s %s permission '%s' should have at most %d parts", requestor, typ, permission, permissionlength[typ]))
				}
			} else {
				if len(parts) != permissionlength[typ] {
					errs = append(errs, fmt.Sprintf("%s %s permission '%s' should have %d parts", requestor, typ, permission, permissionlength[typ]))
				}
			}
			if len(parts) > 0 && parts[0] != "*" {
				if conf.IrmaConfiguration.SchemeManagers[irma.NewSchemeManagerIdentifier(parts[0])] == nil {
					errs = append(errs, fmt.Sprintf("%s %s permission '%s': unknown scheme", requestor, typ, permission))
					continue // no sense in checking if issuer, credtype or attr type are known; they won't be
				}
			}
			if len(parts) > 1 && parts[1] != "*" {
				id := irma.NewIssuerIdentifier(strings.Join(parts[:2], "."))
				if conf.IrmaConfiguration.Issuers[id] == nil {
					errs = append(errs, fmt.Sprintf("%s %s permission '%s': unknown issuer", requestor, typ, permission))
					continue
				}
			}
			if len(parts) > 2 && parts[2] != "*" {
				id := irma.NewCredentialTypeIdentifier(strings.Join(parts[:3], "."))
				if conf.IrmaConfiguration.CredentialTypes[id] == nil {
					errs = append(errs, fmt.Sprintf("%s %s permission '%s': unknown credential type", requestor, typ, permission))
					continue
				}
			}
			if len(parts) > 3 && parts[3] != "*" {
				id := irma.NewAttributeTypeIdentifier(strings.Join(parts[:4], "."))
				if conf.IrmaConfiguration.AttributeTypes[id] == nil {
					errs = append(errs, fmt.Sprintf("%s %s permission '%s': unknown attribute type", requestor, typ, permission))
					continue
				}
			}
		}
	}

	return errs
}

Sietse Ringers's avatar
Sietse Ringers committed
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
func (conf *Configuration) clientTlsConfig() (*tls.Config, error) {
	return conf.readTlsConf(conf.ClientTlsCertificate, conf.ClientTlsCertificateFile, conf.ClientTlsPrivateKey, conf.ClientTlsPrivateKeyFile)
}

func (conf *Configuration) tlsConfig() (*tls.Config, error) {
	return conf.readTlsConf(conf.TlsCertificate, conf.TlsCertificateFile, conf.TlsPrivateKey, conf.TlsPrivateKeyFile)
}

func (conf *Configuration) readTlsConf(cert, certfile, key, keyfile string) (*tls.Config, error) {
	if cert == "" && certfile == "" && key == "" && keyfile == "" {
		return nil, nil
	}

	var certbts, keybts []byte
	var err error
	if certbts, err = fs.ReadKey(cert, certfile); err != nil {
		return nil, err
	}
	if keybts, err = fs.ReadKey(key, keyfile); err != nil {
		return nil, err
	}

	cer, err := tls.X509KeyPair(certbts, keybts)
	if err != nil {
		return nil, err
	}
	return &tls.Config{
		Certificates:             []tls.Certificate{cer},
		MinVersion:               tls.VersionTLS12,
		CurvePreferences:         []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
		PreferServerCipherSuites: true,
		CipherSuites: []uint16{
			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_RSA_WITH_AES_256_CBC_SHA,
		},
	}, nil
}

394
func (conf *Configuration) readPrivateKey() error {
395
	if conf.JwtPrivateKey == "" && conf.JwtPrivateKeyFile == "" {
396
397
398
		return nil
	}

399
400
401
	keybytes, err := fs.ReadKey(conf.JwtPrivateKey, conf.JwtPrivateKeyFile)
	if err != nil {
		return errors.WrapPrefix(err, "failed to read private key", 0)
402
403
	}

404
	conf.jwtPrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(keybytes)
Sietse Ringers's avatar
Sietse Ringers committed
405
	conf.Logger.Info("Private key parsed, JWT endpoints enabled")
406
407
	return err
}
Sietse Ringers's avatar
Sietse Ringers committed
408

409
410
411
412
func (conf *Configuration) separateClientServer() bool {
	return conf.ClientPort != 0
}

Sietse Ringers's avatar
Sietse Ringers committed
413
414
415
416
417
418
419
420
421
// Return true iff query equals an element of strings.
func contains(strings []string, query string) bool {
	for _, s := range strings {
		if s == query {
			return true
		}
	}
	return false
}