auth.go 8.04 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
package irmaserver

import (
	"net/http"
	"strings"
	"time"

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

Sietse Ringers's avatar
Sietse Ringers committed
15
16
17
// Authenticator instances authenticate incoming session requests. Given details of the HTTP
// post done by the requestor, it is checked whether or not the requestor is known and
// allowed to submit session requests.
18
type Authenticator interface {
Sietse Ringers's avatar
Sietse Ringers committed
19
20
21
22
23
24
25
26
27
	// Initialize is called once on server startup for each requestor that uses this authentication method.
	// Used to parse keys or populate caches for later use.
	Initialize(name string, requestor Requestor) error

	// Authenticate checks, given the HTTP header and POST body, if the authenticator is known
	// and allowed to submit session requests. It returns whether or not the current authenticator
	// is applicable to this sesion requests; the request itself; the name of the requestor;
	// or an error (which is only non-nil if applies is true; i.e. this authenticator applies but
	// it was not able to successfully authenticate the request).
28
29
	Authenticate(
		headers http.Header, body []byte,
30
	) (applies bool, request irma.RequestorRequest, requestor string, err *irma.RemoteError)
31
32
33
34
}

type AuthenticationMethod string

Sietse Ringers's avatar
Sietse Ringers committed
35
// Currently supported requestor authentication methods
36
const (
37
	AuthenticationMethodHmac      = "hmac"
38
	AuthenticationMethodPublicKey = "publickey"
39
	AuthenticationMethodToken     = "token"
40
41
42
	AuthenticationMethodNone      = "none"
)

43
44
45
type HmacAuthenticator struct {
	hmackeys map[string]interface{}
}
46
type PublicKeyAuthenticator struct {
47
	publickeys map[string]interface{}
48
49
50
51
52
53
}
type PresharedKeyAuthenticator struct {
	presharedkeys map[string]string
}
type NilAuthenticator struct{}

54
var authenticators map[AuthenticationMethod]Authenticator
55
56
57

func (NilAuthenticator) Authenticate(
	headers http.Header, body []byte,
58
) (bool, irma.RequestorRequest, string, *irma.RemoteError) {
59
60
61
	if headers.Get("Authentication") != "" || !strings.HasPrefix(headers.Get("Content-Type"), "application/json") {
		return false, nil, "", nil
	}
62
	request, err := server.ParseSessionRequest(body)
63
64
65
66
67
68
	if err != nil {
		return true, nil, "", server.RemoteError(server.ErrorInvalidRequest, err.Error())
	}
	return true, request, "", nil
}

69
func (NilAuthenticator) Initialize(name string, requestor Requestor) error {
70
71
72
	return nil
}

73
func (hauth *HmacAuthenticator) Authenticate(
74
	headers http.Header, body []byte,
75
) (applies bool, request irma.RequestorRequest, requestor string, err *irma.RemoteError) {
76
77
	return jwtAuthenticate(headers, body, jwt.SigningMethodHS256.Name, hauth.hmackeys)
}
78

79
80
func (hauth *HmacAuthenticator) Initialize(name string, requestor Requestor) error {
	if requestor.AuthenticationKey == "" {
81
		return errors.Errorf("Requestor %s has no authentication key", name)
82
	}
83

84
85
86
87
	// Read file contents or string contents
	bts, err := fs.ReadKey(requestor.AuthenticationKey)
	if err != nil {
		return err
88
	}
89
90
91

	// We accept any of the base64 encodings
	bts, err = fs.Base64Decode(bts)
92
93
94
95
96
97
98
	if err != nil {
		return errors.WrapPrefix(err, "Failed to base64 decode hmac key of requestor "+name, 0)
	}

	hauth.hmackeys[name] = bts
	return nil

99
}
100

101
102
func (pkauth *PublicKeyAuthenticator) Authenticate(
	headers http.Header, body []byte,
103
) (bool, irma.RequestorRequest, string, *irma.RemoteError) {
104
	return jwtAuthenticate(headers, body, jwt.SigningMethodRS256.Name, pkauth.publickeys)
105
106
}

107
func (pkauth *PublicKeyAuthenticator) Initialize(name string, requestor Requestor) error {
108
109
110
	bts, err := fs.ReadKey(requestor.AuthenticationKey)
	if err != nil {
		return err
111
	}
112
113
114
115
116
117
118
119
120
	if len(bts) == 0 {
		return errors.Errorf("Requestor %s has invalid public key", name)
	}
	pk, err := jwt.ParseRSAPublicKeyFromPEM(bts)
	if err != nil {
		return err
	}
	pkauth.publickeys[name] = pk

121
122
123
124
125
	return nil
}

func (pskauth *PresharedKeyAuthenticator) Authenticate(
	headers http.Header, body []byte,
126
) (bool, irma.RequestorRequest, string, *irma.RemoteError) {
127
128
129
130
131
132
133
134
	auth := headers.Get("Authentication")
	if auth == "" || !strings.HasPrefix(headers.Get("Content-Type"), "application/json") {
		return false, nil, "", nil
	}
	requestor, ok := pskauth.presharedkeys[auth]
	if !ok {
		return true, nil, "", server.RemoteError(server.ErrorUnauthorized, "")
	}
135
	request, err := server.ParseSessionRequest(body)
136
137
138
139
140
141
	if err != nil {
		return true, nil, "", server.RemoteError(server.ErrorInvalidRequest, err.Error())
	}
	return true, request, requestor, nil
}

142
143
func (pskauth *PresharedKeyAuthenticator) Initialize(name string, requestor Requestor) error {
	if requestor.AuthenticationKey == "" {
144
		return errors.Errorf("Requestor %s has no authentication key", name)
145
	}
146
147
148
149
150
	bts, err := fs.ReadKey(requestor.AuthenticationKey)
	if err != nil {
		return err
	}
	pskauth.presharedkeys[string(bts)] = name
151
152
153
154
155
	return nil
}

// Helper functions

156
157
// Given an (unauthenticated) jwt, return the key against which it should be verified using the "kid" header
func jwtKeyExtractor(publickeys map[string]interface{}) func(token *jwt.Token) (interface{}, error) {
158
159
160
161
	return func(token *jwt.Token) (interface{}, error) {
		var ok bool
		kid, ok := token.Header["kid"]
		if !ok {
162
			kid = token.Claims.(*jwt.StandardClaims).Issuer
163
164
165
		}
		requestor, ok := kid.(string)
		if !ok {
166
			return nil, errors.New("requestor name was not a string")
167
		}
168
		token.Claims.(*jwt.StandardClaims).Issuer = requestor
169
170
171
172
173
174
		if pk, ok := publickeys[requestor]; ok {
			return pk, nil
		}
		return nil, errors.Errorf("Unknown requestor: %s", requestor)
	}
}
175
176
177
178

// jwtAuthenticate is a helper function for JWT-based authenticators that verifies and parses JWTs.
func jwtAuthenticate(
	headers http.Header, body []byte, signatureAlg string, keys map[string]interface{},
179
) (bool, irma.RequestorRequest, string, *irma.RemoteError) {
180
181
182
183
184
	// Read JWT and check its type
	if headers.Get("Authorization") != "" || !strings.HasPrefix(headers.Get("Content-Type"), "text/plain") {
		return false, nil, "", nil
	}
	requestorJwt := string(body)
185
186
187
188
189
190
191

	// We need to establish the signature method with which the JWT was signed. We do this by just
	// inspecting the JWT header here, before the signature is verified (which is done below). I suppose
	// it would be more idiomatic to have the KeyFunc which is fed to jwt.ParseWithClaims() perform this
	// task, but then the KeyFunc would need access to all public keys here instead of the ones belonging
	// to the signature algorithm we are expecting (specified by signatureAlg). Security-wise it makes no
	// difference: either way the alg header is examined before the signature is verified.
192
193
194
195
196
197
198
	alg, err := jwtSignatureAlg(requestorJwt)
	if err != nil || alg != signatureAlg {
		// If err != nil, ie. we failed to determine the JWT signature algorithm, we assume that the
		// request is not meant for this authenticator. So we don't return err
		return false, nil, "", nil
	}

199
200
	// Verify JWT signature. We do not yet store the JWT contents here, because we need to know the session type first
	// before we can construct a struct instance of the appropriate type into which to unmarshal the JWT contents.
201
	claims := &jwt.StandardClaims{}
202
	_, err = jwt.ParseWithClaims(requestorJwt, claims, jwtKeyExtractor(keys))
203
204
205
206
207
208
	if err != nil {
		return true, nil, "", server.RemoteError(server.ErrorInvalidRequest, err.Error())
	}
	if !claims.VerifyIssuedAt(time.Now().Unix(), true) {
		return true, nil, "", server.RemoteError(server.ErrorUnauthorized, "jwt not yet valid")
	}
209
	if time.Unix(claims.IssuedAt, 0).Add(time.Duration(conf.MaxRequestAge) * time.Second).Before(time.Now()) {
210
211
212
213
214
215
216
217
218
		return true, nil, "", server.RemoteError(server.ErrorUnauthorized, "jwt too old")
	}

	// Read JWT contents
	parsedJwt, err := irma.ParseRequestorJwt(claims.Subject, requestorJwt)
	if err != nil {
		return true, nil, "", server.RemoteError(server.ErrorInvalidRequest, err.Error())
	}

219
	requestor := claims.Issuer // presence is ensured by jwtKeyExtractor
220
	return true, parsedJwt.RequestorRequest(), requestor, nil
221
222
223
}

func jwtSignatureAlg(j string) (string, error) {
224
225
226
	token, _, err := new(jwt.Parser).ParseUnverified(j, &jwt.StandardClaims{})
	if err != nil {
		return "", err
227
	}
228
	return token.Method.Alg(), nil
229
}