requestor_test.go 13.5 KB
Newer Older
1 2 3
package sessiontest

import (
4
	"bytes"
5
	"encoding/json"
6 7
	"io/ioutil"
	"net/http"
8

9
	"reflect"
10

11 12 13 14
	"testing"

	"github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/test"
15
	"github.com/privacybydesign/irmago/irmaclient"
Sietse Ringers's avatar
Sietse Ringers committed
16
	"github.com/privacybydesign/irmago/server"
17 18 19
	"github.com/stretchr/testify/require"
)

20
type sessionOption int
21

22
const (
23
	sessionOptionUpdatedIrmaConfiguration sessionOption = 1 << iota
24
	sessionOptionUnsatisfiableRequest
25
	sessionOptionRetryPost
26
	sessionOptionIgnoreClientError
27 28 29 30 31
)

type requestorSessionResult struct {
	*server.SessionResult
	Missing irmaclient.MissingAttributes
32
}
33

34
func requestorSessionHelper(t *testing.T, request irma.SessionRequest, client *irmaclient.Client, options ...sessionOption) *requestorSessionResult {
35
	if client == nil {
36
		client, _ = parseStorage(t)
37 38
		defer test.ClearTestStorage(t)
	}
39

40 41 42
	StartIrmaServer(t, len(options) == 1 && options[0] == sessionOptionUpdatedIrmaConfiguration)
	defer StopIrmaServer()

43
	clientChan := make(chan *SessionResult)
Sietse Ringers's avatar
Sietse Ringers committed
44
	serverChan := make(chan *server.SessionResult)
45

46
	qr, token, err := irmaServer.StartSession(request, func(result *server.SessionResult) {
47 48 49 50
		serverChan <- result
	})
	require.NoError(t, err)

51 52 53 54 55
	opts := 0
	for _, o := range options {
		opts |= int(o)
	}

56
	var h irmaclient.Handler
57 58
	if opts&int(sessionOptionUnsatisfiableRequest) > 0 {
		h = &UnsatisfiableTestHandler{TestHandler{t, clientChan, client, nil, ""}}
59
	} else {
60
		h = &TestHandler{t, clientChan, client, nil, ""}
61
	}
62

63 64 65 66
	j, err := json.Marshal(qr)
	require.NoError(t, err)
	client.NewSession(string(j), h)
	clientResult := <-clientChan
67
	if (len(options) == 0 || options[0] != sessionOptionIgnoreClientError) && clientResult != nil {
68 69 70
		require.NoError(t, clientResult.Err)
	}

71 72
	if opts&int(sessionOptionUnsatisfiableRequest) > 0 {
		require.NotNil(t, clientResult)
73 74
		return &requestorSessionResult{nil, clientResult.Missing}
	}
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93

	serverResult := <-serverChan
	require.Equal(t, token, serverResult.Token)

	if opts&int(sessionOptionRetryPost) > 0 {
		req, err := http.NewRequest(http.MethodPost,
			qr.URL+"/proofs",
			bytes.NewBuffer([]byte(h.(*TestHandler).result)),
		)
		require.NoError(t, err)
		req.Header.Add("Content-Type", "application/json")
		res, err := new(http.Client).Do(req)
		require.NoError(t, err)
		require.True(t, res.StatusCode < 300)
		_, err = ioutil.ReadAll(res.Body)
		require.NoError(t, err)
	}

	return &requestorSessionResult{serverResult, nil}
94 95
}

96 97
// Check that nonexistent IRMA identifiers in the session request fail the session
func TestRequestorInvalidRequest(t *testing.T) {
98
	StartIrmaServer(t, false)
99 100 101 102 103 104 105 106
	defer StopIrmaServer()
	_, _, err := irmaServer.StartSession(irma.NewDisclosureRequest(
		irma.NewAttributeTypeIdentifier("irma-demo.RU.foo.bar"),
		irma.NewAttributeTypeIdentifier("irma-demo.baz.qux.abc"),
	), nil)
	require.Error(t, err)
}

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
func TestRequestorDoubleGET(t *testing.T) {
	StartIrmaServer(t, false)
	defer StopIrmaServer()
	qr, _, err := irmaServer.StartSession(irma.NewDisclosureRequest(
		irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID"),
	), nil)
	require.NoError(t, err)

	// Simulate the first GET by the client in the session protocol, twice
	var o interface{}
	transport := irma.NewHTTPTransport(qr.URL)
	transport.SetHeader(irma.MinVersionHeader, "2.5")
	transport.SetHeader(irma.MaxVersionHeader, "2.5")
	require.NoError(t, transport.Get("", &o))
	require.NoError(t, transport.Get("", &o))
}

Sietse Ringers's avatar
Sietse Ringers committed
124
func TestRequestorSignatureSession(t *testing.T) {
125
	client, _ := parseStorage(t)
126 127
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")

128 129 130 131 132 133 134 135 136 137
	var serverResult *requestorSessionResult
	for _, opt := range []sessionOption{0, sessionOptionRetryPost} {
		serverResult = requestorSessionHelper(t, irma.NewSignatureRequest("message", id), client, opt)

		require.Nil(t, serverResult.Err)
		require.Equal(t, irma.ProofStatusValid, serverResult.ProofStatus)
		require.NotEmpty(t, serverResult.Disclosed)
		require.Equal(t, id, serverResult.Disclosed[0][0].Identifier)
		require.Equal(t, "456", serverResult.Disclosed[0][0].Value["en"])
	}
138 139 140 141 142 143 144 145 146 147 148 149

	// Load the updated scheme in which an attribute was added to the studentCard credential type
	schemeid := irma.NewSchemeManagerIdentifier("irma-demo")
	client.Configuration.SchemeManagers[schemeid].URL = "http://localhost:48681/irma_configuration_updated/irma-demo"
	require.NoError(t, client.Configuration.UpdateSchemeManager(schemeid, nil))
	require.NoError(t, client.Configuration.ParseFolder())
	require.Contains(t, client.Configuration.AttributeTypes, irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.newAttribute"))

	// Check that the just created credential is still valid after the new attribute has been added
	_, status, err := serverResult.Signature.Verify(client.Configuration, nil)
	require.NoError(t, err)
	require.Equal(t, irma.ProofStatusValid, status)
150 151
}

Sietse Ringers's avatar
Sietse Ringers committed
152
func TestRequestorDisclosureSession(t *testing.T) {
153
	id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
154
	request := irma.NewDisclosureRequest(id)
155 156 157 158 159 160
	for _, opt := range []sessionOption{0, sessionOptionRetryPost} {
		serverResult := testRequestorDisclosure(t, request, opt)
		require.Len(t, serverResult.Disclosed, 1)
		require.Equal(t, id, serverResult.Disclosed[0][0].Identifier)
		require.Equal(t, "456", serverResult.Disclosed[0][0].Value["en"])
	}
161
}
162

163
func TestRequestorDisclosureMultipleAttrs(t *testing.T) {
164 165 166 167
	request := irma.NewDisclosureRequest(
		irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID"),
		irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.level"),
	)
168 169 170 171
	serverResult := testRequestorDisclosure(t, request)
	require.Len(t, serverResult.Disclosed, 2)
}

172 173
func testRequestorDisclosure(t *testing.T, request *irma.DisclosureRequest, options ...sessionOption) *server.SessionResult {
	serverResult := requestorSessionHelper(t, request, nil, options...)
174 175
	require.Nil(t, serverResult.Err)
	require.Equal(t, irma.ProofStatusValid, serverResult.ProofStatus)
176
	return serverResult.SessionResult
177 178
}

Sietse Ringers's avatar
Sietse Ringers committed
179
func TestRequestorIssuanceSession(t *testing.T) {
180
	testRequestorIssuance(t, false, nil)
181 182
}

183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
func TestRequestorCombinedSessionMultipleAttributes(t *testing.T) {
	var ir irma.IssuanceRequest
	require.NoError(t, irma.UnmarshalValidate([]byte(`{
		"type":"issuing",
		"credentials": [
			{
				"credential":"irma-demo.MijnOverheid.root",
				"attributes" : {
					"BSN":"12345"
				}
			}
		],
		"disclose" : [
			{
				"label":"Initialen",
				"attributes":["irma-demo.RU.studentCard.studentCardNumber"]
			},
			{
				"label":"Achternaam",
				"attributes" : ["irma-demo.RU.studentCard.studentID"]
			},
			{
				"label":"Geboortedatum",
				"attributes":["irma-demo.RU.studentCard.university"]
			}
		]
	}`), &ir))

211
	require.Equal(t, server.StatusDone, requestorSessionHelper(t, &ir, nil).Status)
212 213
}

214
func testRequestorIssuance(t *testing.T, keyshare bool, client *irmaclient.Client) {
215
	attrid := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
216
	request := irma.NewIssuanceRequest([]*irma.CredentialRequest{{
217 218 219 220 221 222 223 224 225 226 227 228
		CredentialTypeID: irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard"),
		Attributes: map[string]string{
			"university":        "Radboud",
			"studentCardNumber": "31415927",
			"studentID":         "s1234567",
			"level":             "42",
		},
	}, {
		CredentialTypeID: irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root"),
		Attributes: map[string]string{
			"BSN": "299792458",
		},
229
	}}, attrid)
230 231 232 233 234 235
	if keyshare {
		request.Credentials = append(request.Credentials, &irma.CredentialRequest{
			CredentialTypeID: irma.NewCredentialTypeIdentifier("test.test.mijnirma"),
			Attributes:       map[string]string{"email": "testusername"},
		})
	}
236

237
	result := requestorSessionHelper(t, request, client)
238 239 240
	require.Nil(t, result.Err)
	require.Equal(t, irma.ProofStatusValid, result.ProofStatus)
	require.NotEmpty(t, result.Disclosed)
241 242 243 244 245
	require.Equal(t, attrid, result.Disclosed[0][0].Identifier)
	require.Equal(t, "456", result.Disclosed[0][0].Value["en"])
}

func TestConDisCon(t *testing.T) {
246
	client, _ := parseStorage(t)
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
	ir := getMultipleIssuanceRequest()
	ir.Credentials = append(ir.Credentials, &irma.CredentialRequest{
		Validity:         ir.Credentials[0].Validity,
		CredentialTypeID: irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.fullName"),
		Attributes: map[string]string{
			"firstnames": "Jan Hendrik",
			"firstname":  "Jan",
			"familyname": "Klaassen",
			"prefix":     "van",
		},
	})
	requestorSessionHelper(t, ir, client)

	dr := irma.NewDisclosureRequest()
	dr.Disclose = irma.AttributeConDisCon{
		irma.AttributeDisCon{
			irma.AttributeCon{
				irma.NewAttributeRequest("irma-demo.MijnOverheid.root.BSN"),
				irma.NewAttributeRequest("irma-demo.MijnOverheid.fullName.firstname"),
				irma.NewAttributeRequest("irma-demo.MijnOverheid.fullName.familyname"),
			},
			irma.AttributeCon{
				irma.NewAttributeRequest("irma-demo.RU.studentCard.studentID"),
				irma.NewAttributeRequest("irma-demo.RU.studentCard.university"),
			},
		},
		//irma.AttributeDisCon{
		//	irma.AttributeCon{
		//		irma.NewAttributeRequest("irma-demo.MijnOverheid.fullName.firstname"),
		//		irma.NewAttributeRequest("irma-demo.MijnOverheid.fullName.familyname"),
		//	},
		//},
	}

	requestorSessionHelper(t, dr, client)
282
}
283 284

func TestOptionalDisclosure(t *testing.T) {
285
	client, _ := parseStorage(t)
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
	university := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.university")
	studentid := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")

	radboud := "Radboud"
	attrs1 := irma.AttributeConDisCon{
		irma.AttributeDisCon{ // Including one non-optional disjunction is required in disclosure and signature sessions
			irma.AttributeCon{irma.AttributeRequest{Type: university}},
		},
		irma.AttributeDisCon{
			irma.AttributeCon{},
			irma.AttributeCon{irma.AttributeRequest{Type: studentid}},
		},
	}
	disclosed1 := [][]*irma.DisclosedAttribute{
		{
			{
302 303 304 305 306
				RawValue:     &radboud,
				Value:        map[string]string{"": radboud, "en": radboud, "nl": radboud},
				Identifier:   irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.university"),
				Status:       irma.AttributeProofStatusPresent,
				IssuanceTime: irma.Timestamp(client.Attributes(university.CredentialTypeIdentifier(), 0).SigningDate()),
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
			},
		},
		{},
	}
	attrs2 := irma.AttributeConDisCon{ // In issuance sessions, it is allowed that all disjunctions are optional
		irma.AttributeDisCon{
			irma.AttributeCon{},
			irma.AttributeCon{irma.AttributeRequest{Type: studentid}},
		},
	}
	disclosed2 := [][]*irma.DisclosedAttribute{{}}

	tests := []struct {
		request   irma.SessionRequest
		attrs     irma.AttributeConDisCon
		disclosed [][]*irma.DisclosedAttribute
	}{
		{irma.NewDisclosureRequest(), attrs1, disclosed1},
		{irma.NewSignatureRequest("message"), attrs1, disclosed1},
		{getIssuanceRequest(true), attrs1, disclosed1},
		{getIssuanceRequest(true), attrs2, disclosed2},
	}

	for _, args := range tests {
		args.request.Disclosure().Disclose = args.attrs

		// TestHandler always prefers the first option when given any choice, so it will not disclose the optional attribute
		result := requestorSessionHelper(t, args.request, client)
		require.True(t, reflect.DeepEqual(args.disclosed, result.Disclosed))
	}
}
338

339
func revocationRequest() irma.SessionRequest {
340 341
	attr := irma.NewAttributeTypeIdentifier("irma-demo.MijnOverheid.root.BSN")
	req := irma.NewDisclosureRequest(attr)
342
	req.Revocation = []irma.CredentialTypeIdentifier{attr.CredentialTypeIdentifier()}
343 344 345 346 347
	return req
}

func revocationSession(t *testing.T, client *irmaclient.Client, options ...sessionOption) *requestorSessionResult {
	result := requestorSessionHelper(t, revocationRequest(), client, options...)
348 349 350 351 352 353
	require.Nil(t, result.Err)
	return result
}

func TestRevocation(t *testing.T) {
	// setup client, constants, and revocation key material
354
	defer test.ClearTestStorage(t)
355 356
	client, _ := parseStorage(t)
	cred := irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root")
357
	StartRevocationServer(t)
358

359
	// issue two MijnOverheid.root instances with revocation enabled
360
	request := irma.NewIssuanceRequest([]*irma.CredentialRequest{{
361 362
		RevocationKey:    "cred0", // once revocation is required for a credential type, this key is required
		CredentialTypeID: cred,
363 364 365 366 367 368
		Attributes: map[string]string{
			"BSN": "299792458",
		},
	}})
	result := requestorSessionHelper(t, request, client)
	require.Nil(t, result.Err)
369 370 371 372 373
	// issue second one which overwrites the first one, as our credtype is a singleton
	// this is ok, as we use cred0 only to revoke it, to see if cred1 keeps working
	request.Credentials[0].RevocationKey = "cred1"
	result = requestorSessionHelper(t, request, client)
	require.Nil(t, result.Err)
374

375
	// perform disclosure session (of cred1) with nonrevocation proof
376 377 378 379
	result = revocationSession(t, client)
	require.Equal(t, irma.ProofStatusValid, result.ProofStatus)
	require.NotEmpty(t, result.Disclosed)

380 381
	req := revocationRequest()
	require.NoError(t, client.Configuration.RevocationStorage.SetRecords(req.Base()))
382
	require.NoError(t, client.NonrevPreprare(req))
383

384 385
	// revoke cred0
	require.NoError(t, revocationServer.Revoke(cred, "cred0"))
386

387
	// perform another disclosure session with nonrevocation proof to see that cred1 still works
388 389 390 391 392
	// client updates its witness to the new accumulator first
	result = revocationSession(t, client)
	require.Equal(t, irma.ProofStatusValid, result.ProofStatus)
	require.NotEmpty(t, result.Disclosed)

393 394
	// revoke cred1
	require.NoError(t, revocationServer.Revoke(cred, "cred1"))
395 396 397 398 399 400

	// try to perform session with revoked credential
	// client notices that is credential is revoked and aborts
	result = revocationSession(t, client, sessionOptionIgnoreClientError)
	require.Equal(t, result.Status, server.StatusCancelled)
}