conf.go 13 KB
Newer Older
1
2
3
4
package irmaserver

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

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
24
25
26
	// 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.
27
	DisableRequestorAuthentication bool `json:"no_auth" mapstructure:"no_auth"`
28

29
	// Address to listen at
30
	ListenAddress string `json:"listen_addr" mapstructure:"listen_addr"`
31
32
	// Port to listen at
	Port int `json:"port" mapstructure:"port"`
Sietse Ringers's avatar
Sietse Ringers committed
33
	// TLS configuration
34
35
36
37
	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"`
38

39
	// If specified, start a separate server for the IRMA app at his port
40
	ClientPort int `json:"client_port" mapstructure:"client_port"`
41
	// If clientport is specified, the server for the IRMA app listens at this address
42
	ClientListenAddress string `json:"client_listen_addr" mapstructure:"client_listen_addr"`
Sietse Ringers's avatar
Sietse Ringers committed
43
	// TLS configuration for irmaclient HTTP API
44
45
46
47
	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"`
48

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

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

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

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

63
64
	Verbose int  `json:"verbose" mapstructure:"verbose"`
	Quiet   bool `json:"quiet" mapstructure:"quiet"`
65
	LogJSON bool `json:"log_json" mapstructure:"log_json"`
Sietse Ringers's avatar
Sietse Ringers committed
66

67
	jwtPrivateKey *rsa.PrivateKey
68
69
}

Sietse Ringers's avatar
Sietse Ringers committed
70
// Permissions specify which attributes or credential a requestor may verify or issue.
71
type Permissions struct {
72
73
74
	Disclosing []string `json:"disclose_perms" mapstructure:"disclose_perms"`
	Signing    []string `json:"sign_perms" mapstructure:"sign_perms"`
	Issuing    []string `json:"issue_perms" mapstructure:"issue_perms"`
75
76
}

Sietse Ringers's avatar
Sietse Ringers committed
77
78
// Requestor contains all configuration (disclosure or verification permissions and authentication)
// for a requestor.
79
type Requestor struct {
80
	Permissions `mapstructure:",squash"`
81

82
	AuthenticationMethod  AuthenticationMethod `json:"auth_method" mapstructure:"auth_method"`
83
	AuthenticationKey     string               `json:"key" mapstructure:"key"`
84
	AuthenticationKeyFile string               `json:"key_file" mapstructure:"key_file"`
85
86
}

Sietse Ringers's avatar
Sietse Ringers committed
87
88
89
90
// 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).
91
func (conf *Configuration) CanIssue(requestor string, creds []*irma.CredentialRequest) (bool, string) {
92
	permissions := append(conf.Requestors[requestor].Issuing, conf.Issuing...)
Sietse Ringers's avatar
Sietse Ringers committed
93
94
95
	if len(permissions) == 0 { // requestor is not present in the permissions
		return false, ""
	}
96
97
98
99
100
101
102
103
104
105
106
107
108
109

	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, ""
110
111
}

Sietse Ringers's avatar
Sietse Ringers committed
112
113
// CanVerifyOrSign returns whether or not the specified requestor may use the selected attributes
// in any of the supported session types.
114
115
116
117
func (conf *Configuration) CanVerifyOrSign(requestor string, action irma.Action, disjunctions irma.AttributeDisjunctionList) (bool, string) {
	var permissions []string
	switch action {
	case irma.ActionDisclosing:
118
		permissions = append(conf.Requestors[requestor].Disclosing, conf.Disclosing...)
119
	case irma.ActionIssuing:
120
		permissions = append(conf.Requestors[requestor].Disclosing, conf.Disclosing...)
121
	case irma.ActionSigning:
122
		permissions = append(conf.Requestors[requestor].Signing, conf.Signing...)
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
	}
	if len(permissions) == 0 { // requestor is not present in the permissions
		return false, ""
	}

	for _, disjunction := range disjunctions {
		for _, attr := range disjunction.Attributes {
			if contains(permissions, "*") ||
				contains(permissions, attr.Root()+".*") ||
				contains(permissions, attr.CredentialTypeIdentifier().IssuerIdentifier().String()+".*") ||
				contains(permissions, attr.CredentialTypeIdentifier().String()+".*") ||
				contains(permissions, attr.String()) {
				continue
			} else {
				return false, attr.String()
			}
		}
	}

	return true, ""
}

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

150
	if conf.DisableRequestorAuthentication {
151
		conf.Logger.Warn("Authentication of incoming session requests disabled: anyone who can reach this server can use it")
152
		authenticators = map[AuthenticationMethod]Authenticator{AuthenticationMethodNone: NilAuthenticator{}}
153
154
	} else {
		authenticators = map[AuthenticationMethod]Authenticator{
155
156
			AuthenticationMethodHmac:      &HmacAuthenticator{hmackeys: map[string]interface{}{}},
			AuthenticationMethodPublicKey: &PublicKeyAuthenticator{publickeys: map[string]interface{}{}},
157
158
			AuthenticationMethodToken:     &PresharedKeyAuthenticator{presharedkeys: map[string]string{}},
		}
159

160
161
162
163
		// Initialize authenticators
		for name, requestor := range conf.Requestors {
			authenticator, ok := authenticators[requestor.AuthenticationMethod]
			if !ok {
164
				return errors.Errorf("Requestor %s has unsupported authentication type", name)
165
166
167
168
169
			}
			if err := authenticator.Initialize(name, requestor); err != nil {
				return err
			}
		}
170
171
	}

172
173
174
175
	if conf.Port <= 0 || conf.Port > 65535 {
		return errors.Errorf("Port must be between 1 and 65535 (was %d)", conf.Port)
	}

176
177
178
179
180
181
182
183
184
185
	if conf.ClientPort != 0 && conf.ClientPort == conf.Port {
		return errors.New("If clientport is given it must be different from port")
	}
	if conf.ClientPort < 0 || conf.ClientPort > 65535 {
		return errors.Errorf("clientport must be between 0 and 65535 (was %d)", conf.ClientPort)
	}
	if conf.ClientListenAddress != "" && conf.ClientPort == 0 {
		return errors.New("clientlistenaddr must be combined with a nonzero clientport")
	}

Sietse Ringers's avatar
Sietse Ringers committed
186
187
188
189
190
191
192
193
194
	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)
	}

195
196
197
198
	if err := conf.validatePermissions(); err != nil {
		return err
	}

199
200
201
	if conf.URL != "" {
		if !strings.HasSuffix(conf.URL, "/") {
			conf.URL = conf.URL + "/"
202
		}
203
		conf.URL = conf.URL + "irma/"
204
		// replace "port" in url with actual port
205
206
207
208
209
		port := conf.ClientPort
		if port == 0 {
			port = conf.Port
		}
		replace := "$1:" + strconv.Itoa(port)
210
		conf.URL = string(regexp.MustCompile("(https?://[^/]*):port").ReplaceAll([]byte(conf.URL), []byte(replace)))
Sietse Ringers's avatar
Sietse Ringers committed
211
212
213
214
215
216
		separateClientServer := conf.separateClientServer()
		if (separateClientServer && clientTlsConf != nil) || (!separateClientServer && tlsConf != nil) {
			if strings.HasPrefix(conf.URL, "http://") {
				conf.URL = "https://" + conf.URL[len("http://"):]
			}
		}
217
218
219
220
221
	}

	return nil
}

222
223
224
225
226
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")
	}

227
	errs := conf.validatePermissionSet("Global", conf.Permissions)
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
	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
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
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
}

333
func (conf *Configuration) readPrivateKey() error {
334
	if conf.JwtPrivateKey == "" && conf.JwtPrivateKeyFile == "" {
335
336
337
		return nil
	}

338
339
340
	keybytes, err := fs.ReadKey(conf.JwtPrivateKey, conf.JwtPrivateKeyFile)
	if err != nil {
		return errors.WrapPrefix(err, "failed to read private key", 0)
341
342
	}

343
	conf.jwtPrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(keybytes)
344
345
	return err
}
Sietse Ringers's avatar
Sietse Ringers committed
346

347
348
349
350
func (conf *Configuration) separateClientServer() bool {
	return conf.ClientPort != 0
}

Sietse Ringers's avatar
Sietse Ringers committed
351
352
353
354
355
356
357
358
359
// 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
}