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

import (
	"crypto/rsa"
5
	"fmt"
6
	"io/ioutil"
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

Sietse Ringers's avatar
Sietse Ringers committed
21
22
23
	// 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.
24
	DisableRequestorAuthentication bool `json:"noauth" mapstructure:"noauth"`
25

26
	// Address to listen at
27
	ListenAddress string `json:"listenaddr" mapstructure:"listenaddr"`
28
29
	// Port to listen at
	Port int `json:"port" mapstructure:"port"`
30

31
32
33
34
35
	// If specified, start a separate server for the IRMA app at his port
	ClientPort int `json:"clientport" mapstructure:"clientport"`
	// If clientport is specified, the server for the IRMA app listens at this address
	ClientListenAddress string `json:"clientlistenaddr" mapstructure:"clientlistenaddr"`

36
37
38
	// Requestor-specific permission and authentication configuration
	RequestorsString string               `json:"-" mapstructure:"requestors"`
	Requestors       map[string]Requestor `json:"requestors"`
39

40
41
	// Disclosing, signing or issuance permissions that apply to all requestors
	GlobalPermissionsString string      `json:"-" mapstructure:"permissions"`
42
	GlobalPermissions       Permissions `json:"permissions" mapstructure:"permissions"`
43

44
45
	// Used in the "iss" field of result JWTs from /result-jwt and /getproof
	JwtIssuer string `json:"jwtissuer" mapstructure:"jwtissuer"`
46

47
48
	// Private key to sign result JWTs with. If absent, /result-jwt and /getproof are disabled.
	JwtPrivateKey string `json:"jwtprivatekey" mapstructure:"jwtprivatekey"`
49

50
51
52
	// Max age in seconds of a session request JWT (using iat field)
	MaxRequestAge int `json:"maxrequestage" mapstructure:"maxrequestage"`

53
54
	Verbose int  `json:"verbose" mapstructure:"verbose"`
	Quiet   bool `json:"quiet" mapstructure:"quiet"`
Sietse Ringers's avatar
Sietse Ringers committed
55

56
	jwtPrivateKey *rsa.PrivateKey
57
58
}

Sietse Ringers's avatar
Sietse Ringers committed
59
// Permissions specify which attributes or credential a requestor may verify or issue.
60
type Permissions struct {
61
62
63
	Disclosing []string `json:"disclose" mapstructure:"disclose"`
	Signing    []string `json:"sign" mapstructure:"sign"`
	Issuing    []string `json:"issue" mapstructure:"issue"`
64
65
}

Sietse Ringers's avatar
Sietse Ringers committed
66
67
// Requestor contains all configuration (disclosure or verification permissions and authentication)
// for a requestor.
68
type Requestor struct {
69
	Permissions `mapstructure:",squash"`
70

71
72
	AuthenticationMethod AuthenticationMethod `json:"authmethod" mapstructure:"authmethod"`
	AuthenticationKey    string               `json:"key" mapstructure:"key"`
73
74
}

Sietse Ringers's avatar
Sietse Ringers committed
75
76
77
78
// 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).
79
80
func (conf *Configuration) CanIssue(requestor string, creds []*irma.CredentialRequest) (bool, string) {
	permissions := append(conf.Requestors[requestor].Issuing, conf.GlobalPermissions.Issuing...)
Sietse Ringers's avatar
Sietse Ringers committed
81
82
83
	if len(permissions) == 0 { // requestor is not present in the permissions
		return false, ""
	}
84
85
86
87
88
89
90
91
92
93
94
95
96
97

	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, ""
98
99
}

Sietse Ringers's avatar
Sietse Ringers committed
100
101
// CanVerifyOrSign returns whether or not the specified requestor may use the selected attributes
// in any of the supported session types.
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
func (conf *Configuration) CanVerifyOrSign(requestor string, action irma.Action, disjunctions irma.AttributeDisjunctionList) (bool, string) {
	var permissions []string
	switch action {
	case irma.ActionDisclosing:
		permissions = append(conf.Requestors[requestor].Disclosing, conf.GlobalPermissions.Disclosing...)
	case irma.ActionIssuing:
		permissions = append(conf.Requestors[requestor].Disclosing, conf.GlobalPermissions.Disclosing...)
	case irma.ActionSigning:
		permissions = append(conf.Requestors[requestor].Signing, conf.GlobalPermissions.Signing...)
	}
	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
	}

138
	if conf.DisableRequestorAuthentication {
139
		conf.Logger.Warn("Authentication of incoming session requests disabled: anyone who can reach this server can use it")
140
		authenticators = map[AuthenticationMethod]Authenticator{AuthenticationMethodNone: NilAuthenticator{}}
141
142
	} else {
		authenticators = map[AuthenticationMethod]Authenticator{
143
144
			AuthenticationMethodHmac:      &HmacAuthenticator{hmackeys: map[string]interface{}{}},
			AuthenticationMethodPublicKey: &PublicKeyAuthenticator{publickeys: map[string]interface{}{}},
145
146
			AuthenticationMethodToken:     &PresharedKeyAuthenticator{presharedkeys: map[string]string{}},
		}
147

148
149
150
151
		// Initialize authenticators
		for name, requestor := range conf.Requestors {
			authenticator, ok := authenticators[requestor.AuthenticationMethod]
			if !ok {
152
				return errors.Errorf("Requestor %s has unsupported authentication type", name)
153
154
155
156
157
			}
			if err := authenticator.Initialize(name, requestor); err != nil {
				return err
			}
		}
158
159
	}

160
161
162
163
	if conf.Port <= 0 || conf.Port > 65535 {
		return errors.Errorf("Port must be between 1 and 65535 (was %d)", conf.Port)
	}

164
165
166
167
168
169
170
171
172
173
	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")
	}

174
175
176
177
	if err := conf.validatePermissions(); err != nil {
		return err
	}

178
179
180
	if conf.URL != "" {
		if !strings.HasSuffix(conf.URL, "/") {
			conf.URL = conf.URL + "/"
181
		}
182
		conf.URL = conf.URL + "irma/"
183
		// replace "port" in url with actual port
184
185
186
187
188
		port := conf.ClientPort
		if port == 0 {
			port = conf.Port
		}
		replace := "$1:" + strconv.Itoa(port)
189
		conf.URL = string(regexp.MustCompile("(https?://[^/]*):port").ReplaceAll([]byte(conf.URL), []byte(replace)))
190
191
192
193
194
	}

	return nil
}

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
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")
	}

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

264
func (conf *Configuration) readPrivateKey() error {
265
	if conf.JwtPrivateKey == "" {
266
267
268
269
270
		return nil
	}

	var keybytes []byte
	var err error
271
272
	if strings.HasPrefix(conf.JwtPrivateKey, "-----BEGIN") {
		keybytes = []byte(conf.JwtPrivateKey)
273
	} else {
274
		if err = fs.AssertPathExists(conf.JwtPrivateKey); err != nil {
275
276
			return err
		}
277
		if keybytes, err = ioutil.ReadFile(conf.JwtPrivateKey); err != nil {
278
279
280
281
			return err
		}
	}

282
	conf.jwtPrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(keybytes)
283
284
	return err
}
Sietse Ringers's avatar
Sietse Ringers committed
285

286
287
288
289
func (conf *Configuration) separateClientServer() bool {
	return conf.ClientPort != 0
}

Sietse Ringers's avatar
Sietse Ringers committed
290
291
292
293
294
295
296
297
298
// 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
}