conf.go 6.55 KB
Newer Older
1
2
3
4
5
6
7
8
package irmaserver

import (
	"crypto/rsa"
	"io/ioutil"
	"strings"

	"github.com/dgrijalva/jwt-go"
9
	"github.com/go-errors/errors"
10
11
12
13
14
15
	"github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/fs"
	"github.com/privacybydesign/irmago/server"
)

type Configuration struct {
16
	*server.Configuration `mapstructure:",squash"`
17

Sietse Ringers's avatar
Sietse Ringers committed
18
19
20
	// 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.
21
22
23
24
25
26
27
28
29
30
31
32
33
	DisableRequestorAuthentication bool `json:"noauth" mapstructure:"noauth"`
	// Port to listen at
	Port int `json:"port" mapstructure:"port"`
	// Requestor-specific permission and authentication configuration
	RequestorsString string               `json:"-" mapstructure:"requestors"`
	Requestors       map[string]Requestor `json:"requestors"`
	// Disclosing, signing or issuance permissions that apply to all requestors
	GlobalPermissionsString string      `json:"-" mapstructure:"permissions"`
	GlobalPermissions       Permissions `json:"permissions"`
	// Used in the "iss" field of result JWTs from /result-jwt and /getproof
	JwtIssuer string `json:"jwtissuer" mapstructure:"jwtissuer"`
	// Private key to sign result JWTs with. If absent, /result-jwt and /getproof are disabled.
	JwtPrivateKey string `json:"jwtprivatekey" mapstructure:"jwtprivatekey"`
34

35
	jwtPrivateKey *rsa.PrivateKey
36
37
}

Sietse Ringers's avatar
Sietse Ringers committed
38
// Permissions specify which attributes or credential a requestor may verify or issue.
39
type Permissions struct {
40
41
42
	Disclosing []string `json:"disclosing" mapstructure:"disclosing"`
	Signing    []string `json:"signing" mapstructure:"signing"`
	Issuing    []string `json:"issuing" mapstructure:"issuing"`
43
44
}

Sietse Ringers's avatar
Sietse Ringers committed
45
46
// Requestor contains all configuration (disclosure or verification permissions and authentication)
// for a requestor.
47
type Requestor struct {
48
	Permissions `mapstructure:",squash"`
49

50
51
	AuthenticationMethod AuthenticationMethod `json:"authmethod" mapstructure:"authmethod"`
	AuthenticationKey    string               `json:"key" mapstructure:"key"`
52
53
}

Sietse Ringers's avatar
Sietse Ringers committed
54
55
56
57
// 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).
58
59
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
60
61
62
	if len(permissions) == 0 { // requestor is not present in the permissions
		return false, ""
	}
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

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

Sietse Ringers's avatar
Sietse Ringers committed
79
80
// CanVerifyOrSign returns whether or not the specified requestor may use the selected attributes
// in any of the supported session types.
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
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
	}

117
	if conf.DisableRequestorAuthentication {
Sietse Ringers's avatar
Sietse Ringers committed
118
		conf.Logger.Warn("Authentication of incoming session requests disabled")
119
		authenticators = map[AuthenticationMethod]Authenticator{AuthenticationMethodNone: NilAuthenticator{}}
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136

		// Leaving the global permission whitelists empty in this mode means enabling it for everyone
		if len(conf.GlobalPermissions.Disclosing) == 0 {
			conf.Logger.Info("No disclosing whitelist found: allowing verification of any attribute")
			conf.GlobalPermissions.Disclosing = []string{"*"}
		}
		if len(conf.GlobalPermissions.Signing) == 0 {
			conf.Logger.Info("No signing whitelist found: allowing attribute-based signature sessions with any attribute")
			conf.GlobalPermissions.Signing = []string{"*"}
		}
		if len(conf.GlobalPermissions.Issuing) == 0 {
			conf.Logger.Info("No issuance whitelist found: allowing issuance of any credential (for which private keys are installed)")
			conf.GlobalPermissions.Issuing = []string{"*"}
		}
		return nil
	}

137
138
	authenticators = map[AuthenticationMethod]Authenticator{
		AuthenticationMethodPublicKey: &PublicKeyAuthenticator{publickeys: map[string]*rsa.PublicKey{}},
139
		AuthenticationMethodToken:     &PresharedKeyAuthenticator{presharedkeys: map[string]string{}},
140
141
	}

Sietse Ringers's avatar
Sietse Ringers committed
142
	// Initialize authenticators
143
144
145
146
147
148
	for name, requestor := range conf.Requestors {
		authenticator, ok := authenticators[requestor.AuthenticationMethod]
		if !ok {
			return errors.Errorf("Requestor %s has unsupported authentication type")
		}
		if err := authenticator.Initialize(name, requestor); err != nil {
149
150
151
152
153
154
155
156
			return err
		}
	}

	return nil
}

func (conf *Configuration) readPrivateKey() error {
157
	if conf.JwtPrivateKey == "" {
158
159
160
161
162
		return nil
	}

	var keybytes []byte
	var err error
163
164
	if strings.HasPrefix(conf.JwtPrivateKey, "-----BEGIN") {
		keybytes = []byte(conf.JwtPrivateKey)
165
	} else {
166
		if err = fs.AssertPathExists(conf.JwtPrivateKey); err != nil {
167
168
			return err
		}
169
		if keybytes, err = ioutil.ReadFile(conf.JwtPrivateKey); err != nil {
170
171
172
173
			return err
		}
	}

174
	conf.jwtPrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(keybytes)
175
176
	return err
}
Sietse Ringers's avatar
Sietse Ringers committed
177
178
179
180
181
182
183
184
185
186

// 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
}