helpers.go 9.2 KB
Newer Older
1
package servercore
Sietse Ringers's avatar
Sietse Ringers committed
2
3

import (
4
	"crypto/sha256"
5
	"encoding/json"
6
	"fmt"
7
	"net/http"
8
	"reflect"
Sietse Ringers's avatar
Sietse Ringers committed
9
10
11
12
	"time"

	"github.com/dgrijalva/jwt-go"
	"github.com/go-errors/errors"
13
	"github.com/privacybydesign/gabi"
14
15
	"github.com/privacybydesign/gabi/big"
	"github.com/privacybydesign/gabi/revocation"
Sietse Ringers's avatar
Sietse Ringers committed
16
	"github.com/privacybydesign/irmago"
Sietse Ringers's avatar
Sietse Ringers committed
17
	"github.com/privacybydesign/irmago/server"
18
	"github.com/sirupsen/logrus"
19
	"gopkg.in/antage/eventsource.v1"
Sietse Ringers's avatar
Sietse Ringers committed
20
21
22
23
24
25
)

// Session helpers

func (session *session) markAlive() {
	session.lastActive = time.Now()
26
	session.conf.Logger.WithFields(logrus.Fields{"session": session.token}).Debugf("Session marked active, expiry delayed")
Sietse Ringers's avatar
Sietse Ringers committed
27
28
}

Sietse Ringers's avatar
Sietse Ringers committed
29
func (session *session) setStatus(status server.Status) {
30
	session.conf.Logger.WithFields(logrus.Fields{"session": session.token, "prevStatus": session.prevStatus, "status": status}).
31
		Info("Session status updated")
Sietse Ringers's avatar
Sietse Ringers committed
32
	session.status = status
Sietse Ringers's avatar
Sietse Ringers committed
33
	session.result.Status = status
34
	session.sessions.update(session)
35
36
37
38
}

func (session *session) onUpdate() {
	if session.evtSource != nil {
39
		session.conf.Logger.WithFields(logrus.Fields{"session": session.token, "status": session.status}).
40
			Debug("Sending status to SSE listeners")
41
42
		// We send JSON like the other APIs, so quote
		session.evtSource.SendEventMessage(fmt.Sprintf(`"%s"`, session.status), "", "")
43
	}
Sietse Ringers's avatar
Sietse Ringers committed
44
45
}

Sietse Ringers's avatar
Sietse Ringers committed
46
47
48
func (session *session) fail(err server.Error, message string) *irma.RemoteError {
	rerr := server.RemoteError(err, message)
	session.setStatus(server.StatusCancelled)
49
	session.result = &server.SessionResult{Err: rerr, Token: session.token, Status: server.StatusCancelled, Type: session.action}
Sietse Ringers's avatar
Sietse Ringers committed
50
51
52
	return rerr
}

53
const retryTimeLimit = 10 * time.Second
54
55
56
57

// checkCache returns a previously cached response, for replaying against multiple requests from
// irmago's retryablehttp client, if:
// - the same was POSTed as last time
58
59
// - last time was not more than 10 seconds ago (retryablehttp client gives up before this)
// - the session status is what it is expected to be when receiving the request for a second time.
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
func (session *session) checkCache(message []byte, expectedStatus server.Status) (int, []byte) {
	if len(session.responseCache.response) > 0 {
		if session.responseCache.sessionStatus != expectedStatus {
			// don't replay a cache value that was set in a previous session state
			session.responseCache = responseCache{}
			return 0, nil
		}
		if sha256.Sum256(session.responseCache.message) != sha256.Sum256(message) ||
			session.lastActive.Before(time.Now().Add(-retryTimeLimit)) ||
			session.status != expectedStatus {
			return server.JsonResponse(nil, session.fail(server.ErrorUnexpectedRequest, ""))
		}
		return session.responseCache.status, session.responseCache.response
	}
	return 0, nil
}

Sietse Ringers's avatar
Sietse Ringers committed
77
78
// Issuance helpers

79
80
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
func (session *session) handleRevocation(
	cred *irma.CredentialRequest, attributes *irma.AttributeList, sk *gabi.PrivateKey,
) (witness *revocation.Witness, nonrevAttr *big.Int, err error) {
	if !session.conf.IrmaConfiguration.CredentialTypes[cred.CredentialTypeID].SupportsRevocation() {
		return
	}
	if _, ours := session.conf.RevocationServers[cred.CredentialTypeID]; !ours {
		// ensure the client always gets an up to date nonrevocation witness
		// TODO: post IssuanceRecord to remote?
		if err = session.conf.IrmaConfiguration.RevocationUpdateDB(cred.CredentialTypeID); err != nil {
			return
		}
	}
	db, err := session.conf.IrmaConfiguration.RevocationDB(cred.CredentialTypeID)
	if err != nil {
		return
	}
	if db.Enabled() {
		if witness, err = sk.RevocationGenerateWitness(&db.Current); err != nil {
			return
		}
		nonrevAttr = witness.E
		if err = db.AddIssuanceRecord(&revocation.IssuanceRecord{
			Key:        cred.RevocationKey,
			Attr:       nonrevAttr,
			Issued:     time.Now().UnixNano(), // or (floored) cred issuance time?
			ValidUntil: attributes.Expiry().UnixNano(),
		}); err != nil {
			return nil, nil, err
		}
	}
	return
}

113
func (s *Server) validateIssuanceRequest(request *irma.IssuanceRequest) error {
Sietse Ringers's avatar
Sietse Ringers committed
114
115
116
	for _, cred := range request.Credentials {
		// Check that we have the appropriate private key
		iss := cred.CredentialTypeID.IssuerIdentifier()
117
		privatekey, err := s.conf.PrivateKey(iss)
118
119
120
121
		if err != nil {
			return err
		}
		if privatekey == nil {
122
			return errors.Errorf("missing private key of issuer %s", iss.String())
Sietse Ringers's avatar
Sietse Ringers committed
123
		}
124
		pubkey, err := s.conf.IrmaConfiguration.PublicKey(iss, int(privatekey.Counter))
Sietse Ringers's avatar
Sietse Ringers committed
125
126
127
128
		if err != nil {
			return err
		}
		if pubkey == nil {
129
			return errors.Errorf("missing public key of issuer %s", iss.String())
Sietse Ringers's avatar
Sietse Ringers committed
130
131
132
		}
		cred.KeyCounter = int(privatekey.Counter)

133
134
135
136
		// Check that the credential is consistent with irma_configuration
		if err := cred.Validate(s.conf.IrmaConfiguration); err != nil {
			return err
		}
137
		if s.conf.IrmaConfiguration.CredentialTypes[cred.CredentialTypeID].SupportsRevocation() {
138
139
140
141
142
143
144
145
146
147
			db, err := s.conf.IrmaConfiguration.RevocationDB(cred.CredentialTypeID)
			if err != nil {
				return err
			}
			if !db.Enabled() {
				s.conf.Logger.WithFields(logrus.Fields{"cred": cred.CredentialTypeID}).Warn("revocation supported in scheme but not enabled")
			} else {
				if len(cred.RevocationKey) == 0 {
					return errors.New("revocationKey field unset on revocable credential")
				}
148
				if exists, err := db.IssuanceRecordExists([]byte(cred.RevocationKey)); err != nil {
149
150
151
152
153
154
155
					return err
				} else if exists {
					return errors.New("revocationKey already used")
				}
			}
		}

Sietse Ringers's avatar
Sietse Ringers committed
156
		// Ensure the credential has an expiry date
157
		defaultValidity := irma.Timestamp(time.Now().AddDate(0, 6, 0))
Sietse Ringers's avatar
Sietse Ringers committed
158
159
160
161
162
163
164
165
166
167
168
		if cred.Validity == nil {
			cred.Validity = &defaultValidity
		}
		if cred.Validity.Before(irma.Timestamp(time.Now())) {
			return errors.New("cannot issue expired credentials")
		}
	}

	return nil
}

Sietse Ringers's avatar
Sietse Ringers committed
169
func (session *session) getProofP(commitments *irma.IssueCommitmentMessage, scheme irma.SchemeManagerIdentifier) (*gabi.ProofP, error) {
Sietse Ringers's avatar
Sietse Ringers committed
170
171
172
173
174
175
176
177
178
	if session.kssProofs == nil {
		session.kssProofs = make(map[irma.SchemeManagerIdentifier]*gabi.ProofP)
	}

	if _, contains := session.kssProofs[scheme]; !contains {
		str, contains := commitments.ProofPjwts[scheme.Name()]
		if !contains {
			return nil, errors.Errorf("no keyshare proof included for scheme %s", scheme.Name())
		}
179
		session.conf.Logger.Debug("Parsing keyshare ProofP JWT: ", str)
Sietse Ringers's avatar
Sietse Ringers committed
180
181
182
183
		claims := &struct {
			jwt.StandardClaims
			ProofP *gabi.ProofP
		}{}
184
		token, err := jwt.ParseWithClaims(str, claims, session.conf.IrmaConfiguration.KeyshareServerKeyFunc(scheme))
Sietse Ringers's avatar
Sietse Ringers committed
185
186
187
188
189
190
191
192
193
194
195
196
		if err != nil {
			return nil, err
		}
		if !token.Valid {
			return nil, errors.Errorf("invalid keyshare proof included for scheme %s", scheme.Name())
		}
		session.kssProofs[scheme] = claims.ProofP
	}

	return session.kssProofs[scheme], nil
}

197
198
199
200
201
202
203
var eventHeaders = [][]byte{[]byte("Access-Control-Allow-Origin: *")}

func (session *session) eventSource() eventsource.EventSource {
	if session.evtSource != nil {
		return session.evtSource
	}

204
	session.conf.Logger.WithFields(logrus.Fields{"session": session.token}).Debug("Making server sent event source")
205
206
207
208
	session.evtSource = eventsource.New(nil, func(_ *http.Request) [][]byte { return eventHeaders })
	return session.evtSource
}

Sietse Ringers's avatar
Sietse Ringers committed
209
210
// Other

211
212
213
214
215
func (session *session) chooseProtocolVersion(minClient, maxClient *irma.ProtocolVersion) (*irma.ProtocolVersion, error) {
	// Set our minimum supported version to 2.5 if condiscon compatibility is required
	minServer := minProtocolVersion
	if !session.legacyCompatible {
		minServer = &irma.ProtocolVersion{2, 5}
Sietse Ringers's avatar
Sietse Ringers committed
216
	}
217
218
219
220
221

	if minClient.AboveVersion(maxProtocolVersion) || maxClient.BelowVersion(minServer) || maxClient.BelowVersion(minClient) {
		return nil, server.LogWarning(errors.Errorf("Protocol version negotiation failed, min=%s max=%s minServer=%s maxServer=%s", minClient.String(), maxClient.String(), minServer.String(), maxProtocolVersion.String()))
	}
	if maxClient.AboveVersion(maxProtocolVersion) {
Sietse Ringers's avatar
Sietse Ringers committed
222
223
		return maxProtocolVersion, nil
	} else {
224
		return maxClient, nil
Sietse Ringers's avatar
Sietse Ringers committed
225
226
	}
}
227

228
229
230
231
232
233
234
235
236
237
238
239
func copyObject(i interface{}) (interface{}, error) {
	cpy := reflect.New(reflect.TypeOf(i).Elem()).Interface()
	bts, err := json.Marshal(i)
	if err != nil {
		return nil, err
	}
	if err = json.Unmarshal(bts, cpy); err != nil {
		return nil, err
	}
	return cpy, nil
}

240
241
// purgeRequest logs the request excluding any attribute values.
func purgeRequest(request irma.RequestorRequest) irma.RequestorRequest {
242
243
244
245
	// We want to log as much as possible of the request, but no attribute values.
	// We cannot just remove them from the request parameter as that would break the calling code.
	// So we create a deep copy of the request from which we can then safely remove whatever we want to.
	// Ugly hack alert: the easiest way to do this seems to be to convert it to JSON and then back.
246
	// As we do not know the precise type of request, we use reflection to create a new instance
247
	// of the same type as request, into which we then unmarshal our copy.
248
249
250
251
	cpy, err := copyObject(request)
	if err != nil {
		panic(err)
	}
252
253

	// Remove required attribute values from any attributes to be disclosed
254
255
256
257
258
259
260
	_ = cpy.(irma.RequestorRequest).SessionRequest().Disclosure().Disclose.Iterate(
		func(attr *irma.AttributeRequest) error {
			attr.Value = nil
			return nil
		},
	)

261
	// Remove attribute values from attributes to be issued
262
263
	if isreq, ok := cpy.(*irma.IdentityProviderRequest); ok {
		for _, cred := range isreq.Request.Credentials {
264
265
266
			cred.Attributes = nil
		}
	}
267
268

	return cpy.(irma.RequestorRequest)
269
}