auth.go 5.63 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package irmaserver

import (
	"crypto/rsa"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/dgrijalva/jwt-go"
	"github.com/go-errors/errors"
	"github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/server"
)

Sietse Ringers's avatar
Sietse Ringers committed
17
18
19
// 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.
20
type Authenticator interface {
Sietse Ringers's avatar
Sietse Ringers committed
21
22
23
24
25
26
27
28
29
	// 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).
30
31
32
33
34
35
36
	Authenticate(
		headers http.Header, body []byte,
	) (applies bool, request irma.SessionRequest, requestor string, err *irma.RemoteError)
}

type AuthenticationMethod string

Sietse Ringers's avatar
Sietse Ringers committed
37
// Currently supported requestor authentication methods
38
39
const (
	AuthenticationMethodPublicKey = "publickey"
40
	AuthenticationMethodToken     = "token"
41
42
43
44
45
46
47
48
49
50
51
	AuthenticationMethodNone      = "none"
)

type PublicKeyAuthenticator struct {
	publickeys map[string]*rsa.PublicKey
}
type PresharedKeyAuthenticator struct {
	presharedkeys map[string]string
}
type NilAuthenticator struct{}

52
var authenticators map[AuthenticationMethod]Authenticator
53
54
55
56
57
58
59

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

67
func (NilAuthenticator) Initialize(name string, requestor Requestor) error {
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
	return nil
}

func (pkauth *PublicKeyAuthenticator) Authenticate(
	headers http.Header, body []byte,
) (bool, irma.SessionRequest, string, *irma.RemoteError) {
	if headers.Get("Authorization") != "" || !strings.HasPrefix(headers.Get("Content-Type"), "text/plain") {
		return false, nil, "", nil
	}

	requestorJwt := string(body)
	claims := &jwt.StandardClaims{}
	token, err := jwt.ParseWithClaims(requestorJwt, claims, jwtPublicKeyExtractor(pkauth.publickeys))
	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")
	}
	if time.Unix(claims.IssuedAt, 0).Add(10 * time.Minute).Before(time.Now()) { // TODO make configurable
		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())
	}

	requestor := token.Header["kid"].(string) // presence in Header and type is already checked by jwtPublicKeyExtractor
	return true, parsedJwt.SessionRequest(), requestor, nil
}

101
102
103
104
105
106
107
108
func (pkauth *PublicKeyAuthenticator) Initialize(name string, requestor Requestor) error {
	var bts []byte
	var err error
	if strings.HasPrefix(requestor.AuthenticationKey, "-----BEGIN") {
		bts = []byte(requestor.AuthenticationKey)
	}
	if _, err := os.Stat(requestor.AuthenticationKey); err == nil {
		bts, err = ioutil.ReadFile(requestor.AuthenticationKey)
109
110
111
112
		if err != nil {
			return err
		}
	}
113
114
115
116
117
118
119
120
121
	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

122
123
124
125
126
127
128
129
130
131
132
133
134
135
	return nil
}

func (pskauth *PresharedKeyAuthenticator) Authenticate(
	headers http.Header, body []byte,
) (bool, irma.SessionRequest, string, *irma.RemoteError) {
	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, "")
	}
136
	request, err := server.ParseSessionRequest(body)
137
138
139
140
141
142
	if err != nil {
		return true, nil, "", server.RemoteError(server.ErrorInvalidRequest, err.Error())
	}
	return true, request, requestor, nil
}

143
144
145
func (pskauth *PresharedKeyAuthenticator) Initialize(name string, requestor Requestor) error {
	if requestor.AuthenticationKey == "" {
		return errors.Errorf("Requestor %s had no authentication key")
146
	}
147
	pskauth.presharedkeys[requestor.AuthenticationKey] = name
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
	return nil
}

// Helper functions

// Given an (unauthenticated) jwt, return the public key against which it should be verified using the "kid" header
func jwtPublicKeyExtractor(publickeys map[string]*rsa.PublicKey) func(token *jwt.Token) (interface{}, error) {
	return func(token *jwt.Token) (interface{}, error) {
		var ok bool
		kid, ok := token.Header["kid"]
		if !ok {
			return nil, errors.New("No kid jwt header found")
		}
		requestor, ok := kid.(string)
		if !ok {
			return nil, errors.New("kid jwt header was not a string")
		}
		if pk, ok := publickeys[requestor]; ok {
			return pk, nil
		}
		return nil, errors.Errorf("Unknown requestor: %s", requestor)
	}
}