revocation.go 25.3 KB
Newer Older
1
2
3
package irma

import (
4
	"database/sql/driver"
5
	"fmt"
6
	"sort"
7
8
	"time"

9
	"github.com/fxamacker/cbor"
10
	"github.com/getsentry/raven-go"
11
	"github.com/go-errors/errors"
12
	"github.com/hashicorp/go-multierror"
13
	"github.com/jinzhu/gorm"
14
	"github.com/privacybydesign/gabi"
15
	"github.com/privacybydesign/gabi/big"
16
	"github.com/privacybydesign/gabi/revocation"
17
	"github.com/privacybydesign/gabi/signed"
18
19
20

	_ "github.com/jinzhu/gorm/dialects/mysql"
	_ "github.com/jinzhu/gorm/dialects/postgres"
21
22
)

23
type (
Sietse Ringers's avatar
Sietse Ringers committed
24
25
26
	// RevocationStorage stores and retrieves revocation-related data from and to a SQL database,
	// and offers a revocation API for all other irmago code, including a Revoke() method that
	// revokes an earlier issued credential.
27
28
29
30
31
32
33
34
35
	RevocationStorage struct {
		conf     *Configuration
		db       revStorage
		memdb    memRevStorage
		sqlMode  bool
		settings map[CredentialTypeIdentifier]*RevocationSetting

		Keys   RevocationKeys
		client RevocationClient
36
37
	}

Sietse Ringers's avatar
Sietse Ringers committed
38
	// RevocationClient offers an HTTP client to the revocation server endpoints.
39
40
41
42
	RevocationClient struct {
		Conf *Configuration
	}

Sietse Ringers's avatar
Sietse Ringers committed
43
44
	// RevocationKeys contains helper functions for retrieving revocation private and public keys
	// from an irma.Configuration instance.
45
46
47
48
	RevocationKeys struct {
		Conf *Configuration
	}

Sietse Ringers's avatar
Sietse Ringers committed
49
	// RevocationSetting contains revocation settings for a given credential type.
50
	RevocationSetting struct {
51
		Mode                     RevocationMode `json:"mode" mapstructure:"mode"`
52
		PostURLs                 []string       `json:"post_urls" mapstructure:"post_urls"`
53
		ServerURL                string         `json:"server_url" mapstructure:"server_url"`
54
		MaxNonrevocationDuration uint           `json:"max_nonrev_duration" mapstructure:"max_nonrev_duration"` // in seconds, min 30
55

56
57
		// set to now whenever a new update is received, or when the RA indicates
		// there are no new updates. Thus it specifies up to what time our nonrevocation
58
59
		// guarantees lasts.
		updated time.Time
60
61
	}

Sietse Ringers's avatar
Sietse Ringers committed
62
63
	// RevocationMode specifies for a given credential type what revocation operations are
	// supported, and how the associated data is stored (SQL or memory).
64
	RevocationMode string
65
)
66

67
// Structs corresponding to SQL table rows, ending in Record
68
type (
69
70
71
72
73
74
75
	// signedMessage is a signed.Message with DB (un)marshaling methods.
	signedMessage signed.Message
	// RevocationAttribute is a big.Int with DB (un)marshaling methods.
	RevocationAttribute big.Int
	// eventHash is a revocation.Hash with DB (un)marshaling methods.
	eventHash revocation.Hash

76
	AccumulatorRecord struct {
77
78
79
		CredType  CredentialTypeIdentifier `gorm:"primary_key"`
		Data      signedMessage
		PKCounter uint `gorm:"primary_key;auto_increment:false"`
80
81
	}

82
	EventRecord struct {
83
84
		Index      uint64                   `gorm:"primary_key;column:eventindex"`
		CredType   CredentialTypeIdentifier `gorm:"primary_key"`
85
		PKCounter  uint                     `gorm:"primary_key;auto_increment:false"`
86
87
		E          *RevocationAttribute
		ParentHash eventHash
88
89
90
91
	}

	// IssuanceRecord contains information generated during issuance, needed for later revocation.
	IssuanceRecord struct {
92
		Key        string                   `gorm:"primary_key;column:revocationkey"`
93
		CredType   CredentialTypeIdentifier `gorm:"primary_key"`
94
		PKCounter  uint
95
		Attr       *RevocationAttribute
96
97
		Issued     int64
		ValidUntil int64
98
		RevokedAt  int64 `json:",omitempty"` // 0 if not currently revoked
99
	}
100
)
101

102
const (
Sietse Ringers's avatar
Sietse Ringers committed
103
104
	// RevocationModeRequestor is the default revocation mode in which only RevocationRecord instances
	// are consumed for issuance or verification. Uses an in-memory store.
105
	RevocationModeRequestor RevocationMode = ""
Sietse Ringers's avatar
Sietse Ringers committed
106
107

	// RevocationModeProxy indicates that this server
108
109
	// (1) allows fetching of revocation update messages from its database,
	// (2) relays all revocation updates it receives to the URLs configured in the containing
Sietse Ringers's avatar
Sietse Ringers committed
110
	// RevocationSetting struct.
111
	// Requires a SQL server to store and retrieve update messages from.
Sietse Ringers's avatar
Sietse Ringers committed
112
113
	RevocationModeProxy RevocationMode = "proxy"

114
	// RevocationModeServer indicates that this is a revocation server for a credential type.
Sietse Ringers's avatar
Sietse Ringers committed
115
116
117
	// IssuanceRecord instances are sent to this server, as well as revocation commands, through
	// revocation sessions or through the RevocationStorage.Revoke() method.
	// Requires a SQL server to store and retrieve all records from and requires the issuer's
118
	// private key to be accessible, in order to revoke and to sign new revocation update messages.
119
	// In addition this mode exposes the same endpoints as RevocationModeProxy.
Sietse Ringers's avatar
Sietse Ringers committed
120
121
	RevocationModeServer RevocationMode = "server"

122
	// RevocationDefaultEventCount specifies how many revocation events are attached to session requests
Sietse Ringers's avatar
Sietse Ringers committed
123
	// for the client to update its revocation state.
124
	RevocationDefaultEventCount = 5
125

126
127
128
	// revocationMaxAccumulatorAge is the default maximum in seconds for the 'accumulator age',
	// which we define to be the amount of time since the last confirmation from the RA that the
	// latest accumulator that we know is still the latest one: clients should prove nonrevocation
129
	// against a 'younger' accumulator.
130
	revocationMaxAccumulatorAge uint = 5 * 60
131
132
133
134

	// If server mode is enabled for a credential type, then once every so many seconds
	// the timestamp in each accumulator is updated to now.
	revocationAccumulatorUpdateInterval uint64 = 10
135
136
)

Sietse Ringers's avatar
Sietse Ringers committed
137
138
139
// EnableRevocation creates an initial accumulator for a given credential type. This function is the
// only way to create such an initial accumulator and it must be called before anyone can use
// revocation for this credential type. Requires the issuer private key.
140
func (rs *RevocationStorage) EnableRevocation(typ CredentialTypeIdentifier, sk *revocation.PrivateKey) error {
141
	enabled, err := rs.RevocationEnabled(typ)
142
143
144
	if err != nil {
		return err
	}
145
146
	if enabled {
		return errors.New("revocation already enabled")
147
148
	}

149
	update, err := revocation.NewAccumulator(sk)
150
151
152
	if err != nil {
		return err
	}
153

154
	if err = rs.addUpdate(rs.db, typ, update, true); err != nil {
155
156
157
158
159
		return err
	}
	return nil
}

Sietse Ringers's avatar
Sietse Ringers committed
160
161
162
163
// RevocationEnabled returns whether or not revocation is enabled for the given credential type,
// by checking if any revocation record exists in the database.
func (rs *RevocationStorage) RevocationEnabled(typ CredentialTypeIdentifier) (bool, error) {
	if rs.sqlMode {
164
		return rs.db.Exists((*EventRecord)(nil), nil)
Sietse Ringers's avatar
Sietse Ringers committed
165
166
167
168
169
	} else {
		return rs.memdb.HasRecords(typ), nil
	}
}

170
171
172
// Revocation update message methods

// UpdateFrom returns all records that a client requires to update its revocation state if it is currently
173
174
// at the specified index, that is, all records whose end index is greater than or equal to
// the specified index.
175
func (rs *RevocationStorage) UpdateFrom(typ CredentialTypeIdentifier, pkcounter uint, index uint64) (*revocation.Update, error) {
176
177
178
	// Only requires SQL implementation
	var update *revocation.Update
	if err := rs.db.Transaction(func(tx revStorage) error {
179
		acc, _, err := rs.accumulator(tx, typ, pkcounter)
180
181
182
183
		if err != nil {
			return err
		}
		var events []*EventRecord
184
		if err := tx.Find(&events, "cred_type = ? and pk_counter = ? and eventindex >= ?", typ, pkcounter, index); err != nil {
185
186
187
188
189
190
191
192
			return err
		}
		update = rs.newUpdate(acc, events)
		return nil
	}); err != nil {
		return nil, err
	}
	return update, nil
193
194
}

195
func (rs *RevocationStorage) UpdateLatest(typ CredentialTypeIdentifier, count uint64) (map[uint]*revocation.Update, error) {
196
	// TODO what should this function and UpdateFrom return when no records are found?
197
	if rs.sqlMode {
198
		var update map[uint]*revocation.Update
199
		if err := rs.db.Transaction(func(tx revStorage) error {
200
201
202
203
204
			var (
				records []*AccumulatorRecord
				events  []*EventRecord
			)
			if err := tx.Last(&records, map[string]interface{}{"cred_type": typ}); err != nil {
205
206
				return err
			}
207
			if err := tx.Latest(&events, count, map[string]interface{}{"cred_type": typ}); err != nil {
208
209
				return err
			}
210
			update = rs.newUpdates(records, events)
211
212
			return nil
		}); err != nil {
213
214
			return nil, err
		}
215
		return update, nil
216
	} else {
217
		return rs.memdb.Latest(typ, count), nil
218
219
220
	}
}

221
222
223
func (*RevocationStorage) newUpdates(records []*AccumulatorRecord, events []*EventRecord) map[uint]*revocation.Update {
	accs := map[uint]*revocation.SignedAccumulator{}
	for _, r := range records {
224
		accs[r.PKCounter] = r.SignedAccumulator()
225
226
227
	}
	updates := make(map[uint]*revocation.Update, len(accs))
	for _, e := range events {
228
		i := e.PKCounter
229
230
231
232
233
234
235
236
237
238
		if accs[i] == nil {
			continue
		}
		update, present := updates[i]
		if !present {
			update = &revocation.Update{SignedAccumulator: accs[i]}
			updates[i] = update
		}
		update.Events = append(update.Events, e.Event())
	}
239
240
241
242
243
	for _, update := range updates {
		sort.Slice(update.Events, func(i, j int) bool {
			return update.Events[i].Index < update.Events[j].Index
		})
	}
244
245
246
247
	return updates
}

func (rs *RevocationStorage) newUpdate(acc *revocation.SignedAccumulator, events []*EventRecord) *revocation.Update {
248
249
	updates := make([]*revocation.Event, len(events))
	for i := range events {
250
		updates[i] = events[i].Event()
251
	}
252
253
254
	return &revocation.Update{
		SignedAccumulator: acc,
		Events:            updates,
255
	}
256
257
}

258
func (rs *RevocationStorage) AddUpdate(typ CredentialTypeIdentifier, record *revocation.Update) error {
259
	return rs.addUpdate(rs.db, typ, record, false)
260
}
261

262
func (rs *RevocationStorage) addUpdate(tx revStorage, typ CredentialTypeIdentifier, update *revocation.Update, create bool) error {
263
	// Unmarshal and verify the record against the appropriate public key
264
	pk, err := rs.Keys.PublicKey(typ.IssuerIdentifier(), update.SignedAccumulator.PKCounter)
265
266
267
	if err != nil {
		return err
	}
268
	if _, _, err = update.Verify(pk, 0); err != nil {
269
270
271
		return err
	}

272
273
	// Save record
	if rs.sqlMode {
274
275
276
277
		save := tx.Save
		if create {
			save = tx.Insert
		}
278
		if err = save(new(AccumulatorRecord).Convert(typ, update.SignedAccumulator)); err != nil {
279
			return err
280
		}
281
		for _, event := range update.Events {
282
			if err = tx.Insert(new(EventRecord).Convert(typ, update.SignedAccumulator.PKCounter, event)); err != nil {
283
284
285
				return err
			}
		}
286
	} else {
287
		rs.memdb.Insert(typ, update)
288
289
	}

290
	s := rs.getSettings(typ)
291
	s.updated = time.Now()
292
293
	// POST record to listeners, if any, asynchroniously
	go rs.client.PostUpdate(typ, s.PostURLs, update)
294
295
296

	return nil
}
297

298
299
300
// Issuance records

func (rs *RevocationStorage) IssuanceRecordExists(typ CredentialTypeIdentifier, key []byte) (bool, error) {
301
	return rs.db.Exists(&IssuanceRecord{}, map[string]interface{}{"cred_type": typ, "revocationkey": key})
302
303
}

304
305
func (rs *RevocationStorage) AddIssuanceRecord(r *IssuanceRecord) error {
	return rs.db.Insert(r)
306
307
}

308
309
func (rs *RevocationStorage) IssuanceRecord(typ CredentialTypeIdentifier, key []byte) (*IssuanceRecord, error) {
	var r IssuanceRecord
310
	err := rs.db.Last(&r, map[string]interface{}{"cred_type": typ, "revocationkey": key})
311
312
	if err != nil {
		return nil, err
313
	}
314
315
	return &r, nil
}
316

317
318
// Revocation methods

Sietse Ringers's avatar
Sietse Ringers committed
319
320
// Revoke revokes the credential specified by key if found within the current database,
// by updating its revocation time to now, removing its revocation attribute from the current accumulator,
321
// and updating the revocation database on disk.
322
func (rs *RevocationStorage) Revoke(typ CredentialTypeIdentifier, key string) error {
Sietse Ringers's avatar
Sietse Ringers committed
323
324
	if rs.getSettings(typ).Mode != RevocationModeServer {
		return errors.Errorf("cannot revoke %s", typ)
325
	}
326

327
328
	return rs.db.Transaction(func(tx revStorage) error {
		var err error
329
		issrecord := IssuanceRecord{}
330
		if err = tx.Last(&issrecord, map[string]interface{}{"cred_type": typ, "revocationkey": key}); err != nil {
331
332
			return err
		}
333
334
		issrecord.RevokedAt = time.Now().UnixNano()
		if err = tx.Save(&issrecord); err != nil {
335
336
			return err
		}
337
		sk, err := rs.Keys.PrivateKey(typ.IssuerIdentifier(), issrecord.PKCounter)
338
339
340
		if err != nil {
			return err
		}
341
		return rs.revokeAttr(tx, typ, sk, issrecord.Attr)
342
343
344
	})
}

345
func (rs *RevocationStorage) revokeAttr(tx revStorage, typ CredentialTypeIdentifier, sk *revocation.PrivateKey, e *RevocationAttribute) error {
346
	_, cur, err := rs.accumulator(tx, typ, sk.Counter)
347
348
349
350
351
352
	if err != nil {
		return err
	}
	if cur == nil {
		return errors.Errorf("cannot revoke for type %s, not enabled yet", typ)
	}
353
	var parent EventRecord
354
	if err = rs.db.Last(&parent, map[string]interface{}{"cred_type": typ, "pk_counter": sk.Counter}); err != nil {
355
356
		return err
	}
357

358
	update, err := cur.Remove(sk, (*big.Int)(e), parent.Event())
359
360
361
	if err != nil {
		return err
	}
362
	if err = rs.addUpdate(tx, typ, update, false); err != nil {
363
364
365
366
367
		return err
	}
	return nil
}

368
// Accumulator methods
369

370
371
372
373
374
375
func (rs *RevocationStorage) Accumulator(typ CredentialTypeIdentifier, pkcounter uint) (
	*revocation.SignedAccumulator, *revocation.Accumulator, error,
) {
	return rs.accumulator(rs.db, typ, pkcounter)
}

376
// accumulator retrieves, verifies and deserializes the accumulator of the given type and key.
377
func (rs *RevocationStorage) accumulator(tx revStorage, typ CredentialTypeIdentifier, pkcounter uint) (
378
379
380
	*revocation.SignedAccumulator, *revocation.Accumulator, error,
) {
	var err error
381
	var sacc *revocation.SignedAccumulator
382
	if rs.sqlMode {
383
		record := &AccumulatorRecord{}
384
		if err = tx.Last(record, map[string]interface{}{"cred_type": typ, "pk_counter": pkcounter}); err != nil {
385
			if gorm.IsRecordNotFoundError(err) {
386
				return nil, nil, nil
387
			}
388
		}
389
		sacc = record.SignedAccumulator()
390
	} else {
391
		sacc = rs.memdb.SignedAccumulator(typ, pkcounter)
392
		if sacc == nil {
393
			return nil, nil, nil
394
		}
395
396
	}

397
	pk, err := rs.Keys.PublicKey(typ.IssuerIdentifier(), sacc.PKCounter)
398
	if err != nil {
399
		return nil, nil, err
400
	}
401
	acc, err := sacc.UnmarshalVerify(pk)
402
403
	if err != nil {
		return nil, nil, err
404
	}
405
	return sacc, acc, nil
406
407
}

408
409
410
// Methods to update from remote revocation server

func (rs *RevocationStorage) UpdateDB(typ CredentialTypeIdentifier) error {
411
	updates, err := rs.client.FetchUpdateLatest(typ, RevocationDefaultEventCount)
412
	if err != nil {
413
		return err
414
	}
415
416
417
418
	for _, u := range updates {
		if err = rs.AddUpdate(typ, u); err != nil {
			return err
		}
419
420
421
422
	}
	// bump updated even if no new records were added
	rs.getSettings(typ).updated = time.Now()
	return nil
423
424
}

Sietse Ringers's avatar
Sietse Ringers committed
425
func (rs *RevocationStorage) UpdateIfOld(typ CredentialTypeIdentifier) error {
426
	settings := rs.getSettings(typ)
427
	// update 10 seconds before the maximum, to stay below it
428
	if settings.updated.Before(time.Now().Add(time.Duration(-settings.MaxNonrevocationDuration+10) * time.Second)) {
429
		Logger.WithField("credtype", typ).Tracef("fetching revocation updates")
430
		if err := rs.UpdateDB(typ); err != nil {
431
432
433
434
435
436
			return err
		}
	}
	return nil
}

437
438
// SaveIssuanceRecord either stores the issuance record locally, if we are the revocation server of
// the crecential type, or it signs and sends it to the remote revocation server.
439
440
441
442
443
444
445
446
447
func (rs *RevocationStorage) SaveIssuanceRecord(typ CredentialTypeIdentifier, rec *IssuanceRecord, sk *gabi.PrivateKey) error {
	credtype := rs.conf.CredentialTypes[typ]
	if credtype == nil {
		return errors.New("unknown credential type")
	}
	if !credtype.SupportsRevocation() {
		return errors.New("cannot save issuance record: credential type does not support revocation")
	}

448
	// Just store it if we are the revocation server for this credential type
449
450
	settings := rs.getSettings(typ)
	if settings.Mode == RevocationModeServer {
451
		return rs.AddIssuanceRecord(rec)
452
453
	}

454
	// We have to send it, sign it first
455
456
457
	if settings.ServerURL == "" {
		return errors.New("cannot send issuance record: no server_url configured")
	}
458
	rsk, err := sk.RevocationKey()
459
460
461
	if err != nil {
		return err
	}
462
	return rs.client.PostIssuanceRecord(typ, rsk, rec, settings.ServerURL)
463
464
}

465
466
// Misscelaneous methods

467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
func (rs *RevocationStorage) updateAccumulatorTimes(types []CredentialTypeIdentifier) error {
	return rs.db.Transaction(func(tx revStorage) error {
		var err error
		var records []AccumulatorRecord
		Logger.Tracef("updating accumulator times")
		if err = tx.Find(&records, "cred_type in (?)", types); err != nil {
			return err
		}
		for _, r := range records {
			pk, err := rs.Keys.PublicKey(r.CredType.IssuerIdentifier(), r.PKCounter)
			if err != nil {
				return err
			}
			sk, err := rs.Keys.PrivateKey(r.CredType.IssuerIdentifier(), r.PKCounter)
			if err != nil {
				return err
			}
			acc, err := r.SignedAccumulator().UnmarshalVerify(pk)
			if err != nil {
				return err
			}
			acc.Time = time.Now().Unix()
			sacc, err := acc.Sign(sk)
			if err != nil {
				return err
			}
			r.Data = signedMessage(sacc.Data)
			if err = tx.Save(r); err != nil {
				return err
			}
		}
		return nil
	})
}

502
func (rs *RevocationStorage) Load(debug bool, dbtype, connstr string, settings map[CredentialTypeIdentifier]*RevocationSetting) error {
503
	var t *CredentialTypeIdentifier
504
	var ourtypes []CredentialTypeIdentifier
505
506
	for typ, s := range settings {
		switch s.Mode {
507
508
509
510
		case RevocationModeServer:
			if s.ServerURL != "" {
				return errors.New("server_url cannot be combined with server mode")
			}
511
			ourtypes = append(ourtypes, typ)
512
			t = &typ
513
514
515
		case RevocationModeProxy:
			t = &typ
		case RevocationModeRequestor: // noop
516
		default:
517
518
			return errors.Errorf(`invalid revocation mode "%s" for %s (supported: "%s", "%s", "%s")`,
				s.Mode, typ, RevocationModeRequestor, RevocationModeServer, RevocationModeProxy)
519
		}
520
	}
521
522
523
	if t != nil && connstr == "" {
		return errors.Errorf("revocation mode for %s requires SQL database but no connection string given", *t)
	}
524

525
526
527
528
529
530
531
532
533
	if len(ourtypes) > 0 {
		rs.conf.Scheduler.Every(revocationAccumulatorUpdateInterval).Seconds().Do(func() {
			if err := rs.updateAccumulatorTimes(ourtypes); err != nil {
				err = errors.WrapPrefix(err, "failed to write updated accumulator record", 0)
				raven.CaptureError(err, nil)
			}
		})
	}

534
535
536
537
538
539
	if connstr == "" {
		Logger.Trace("Using memory revocation database")
		rs.memdb = newMemStorage()
		rs.sqlMode = false
	} else {
		Logger.Trace("Connecting to revocation SQL database")
540
		db, err := newSqlStorage(debug, dbtype, connstr)
541
		if err != nil {
542
			return err
543
		}
544
545
		rs.db = db
		rs.sqlMode = true
546
	}
547
548
549
550
551
	if settings != nil {
		rs.settings = settings
	} else {
		rs.settings = map[CredentialTypeIdentifier]*RevocationSetting{}
	}
552
553
554
555
556
557
	for id, settings := range rs.settings {
		if settings.MaxNonrevocationDuration != 0 && settings.MaxNonrevocationDuration < 30 {
			return errors.Errorf("max_nonrev_duration setting for %s must be at least 30 seconds, was %d",
				id, settings.MaxNonrevocationDuration)
		}
	}
558
559
560
	rs.client = RevocationClient{Conf: rs.conf}
	rs.Keys = RevocationKeys{Conf: rs.conf}
	return nil
561
562
}

563
564
565
func (rs *RevocationStorage) Close() error {
	if rs.db != nil {
		return rs.db.Close()
566
567
568
569
	}
	return nil
}

570
// SetRevocationUpdates retrieves the latest revocation records from the database, and attaches
571
572
// them to the request, for each credential type for which a nonrevocation proof is requested in
// b.Revocation.
573
func (rs *RevocationStorage) SetRevocationUpdates(b *BaseRequest) error {
574
575
	if len(b.Revocation) == 0 {
		return nil
576
	}
577
	var err error
578
	b.RevocationUpdates = make(map[CredentialTypeIdentifier]map[uint]*revocation.Update, len(b.Revocation))
579
	for _, credid := range b.Revocation {
580
581
582
		if !rs.conf.CredentialTypes[credid].SupportsRevocation() {
			return errors.Errorf("cannot request nonrevocation proof for %s: revocation not enabled in scheme")
		}
Sietse Ringers's avatar
Sietse Ringers committed
583
		if err = rs.UpdateIfOld(credid); err != nil {
584
585
586
587
588
589
590
591
592
593
594
595
			updated := rs.getSettings(credid).updated
			if !updated.IsZero() {
				Logger.Warnf("failed to fetch revocation updates for %s, nonrevocation is guaranteed only until %s ago:",
					credid, time.Now().Sub(updated).String())
				Logger.Warn(err)
			} else {
				Logger.Errorf("revocation is disabled for %s: failed to fetch revocation updates and none are known locally", credid)
				Logger.Warn(err)
				// We can offer no nonrevocation guarantees at all while the requestor explicitly
				// asked for it; fail the session by returning an error
				return err
			}
596
		}
597
		b.RevocationUpdates[credid], err = rs.UpdateLatest(credid, RevocationDefaultEventCount)
598
599
600
		if err != nil {
			return err
		}
601
	}
602
603
604
605
606
607
	return nil
}

func (rs *RevocationStorage) getSettings(typ CredentialTypeIdentifier) *RevocationSetting {
	if rs.settings[typ] == nil {
		rs.settings[typ] = &RevocationSetting{}
608
	}
609
610
611
612
613
	s := rs.settings[typ]
	if s.MaxNonrevocationDuration == 0 {
		s.MaxNonrevocationDuration = revocationMaxAccumulatorAge
	}
	return s
614
615
}

616
func (client RevocationClient) PostUpdate(typ CredentialTypeIdentifier, urls []string, update *revocation.Update) {
617
	transport := NewHTTPTransport("")
618
	transport.Binary = true
619
	for _, url := range urls {
620
621
		err := transport.Post(fmt.Sprintf("%s/revocation/update/%s", url, typ.String()), nil, update)
		if err != nil {
622
623
			Logger.Warn("error sending revocation update", err)
		}
624
	}
625
626
}

627
628
629
630
631
func (client RevocationClient) PostIssuanceRecord(typ CredentialTypeIdentifier, sk *revocation.PrivateKey, rec *IssuanceRecord, url string) error {
	message, err := signed.MarshalSign(sk.ECDSA, rec)
	if err != nil {
		return err
	}
632
	return NewHTTPTransport(url).Post(
633
		fmt.Sprintf("revocation/issuancerecord/%s/%d", typ, sk.Counter), nil, []byte(message),
634
635
636
637
	)
}

// FetchRevocationRecords gets revocation update messages from the revocation server, of the specified index and greater.
638
func (client RevocationClient) FetchUpdateFrom(typ CredentialTypeIdentifier, pkcounter uint, index uint64) (*revocation.Update, error) {
639
640
641
	update := &revocation.Update{}
	return update, client.getMultiple(
		client.Conf.CredentialTypes[typ].RevocationServers,
642
		fmt.Sprintf("revocation/updatefrom/%s/%d/%d", typ, pkcounter, index),
643
644
		&update,
	)
645
}
646

647
648
649
650
651
652
653
func (client RevocationClient) FetchUpdateLatest(typ CredentialTypeIdentifier, count uint64) (map[uint]*revocation.Update, error) {
	update := map[uint]*revocation.Update{}
	return update, client.getMultiple(
		client.Conf.CredentialTypes[typ].RevocationServers,
		fmt.Sprintf("revocation/updatelatest/%s/%d", typ, count),
		&update,
	)
654
655
}

656
func (RevocationClient) getMultiple(urls []string, path string, dest interface{}) error {
657
658
659
660
661
	var (
		errs      multierror.Error
		transport = NewHTTPTransport("")
	)
	transport.Binary = true
662
	for _, url := range urls {
663
		transport.Server = url
664
		err := transport.Get(path, dest)
665
		if err == nil {
666
			return nil
667
668
669
		} else {
			errs.Errors = append(errs.Errors, err)
		}
670
	}
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
	return &errs
}

func (rs RevocationKeys) PrivateKeyLatest(issid IssuerIdentifier) (*revocation.PrivateKey, error) {
	sk, err := rs.Conf.PrivateKeyLatest(issid)
	if err != nil {
		return nil, err
	}
	if sk == nil {
		return nil, errors.Errorf("unknown private key: %s", issid)
	}
	revsk, err := sk.RevocationKey()
	if err != nil {
		return nil, err
	}
	return revsk, nil
687
688
}

689
func (rs RevocationKeys) PrivateKey(issid IssuerIdentifier, counter uint) (*revocation.PrivateKey, error) {
690
	sk, err := rs.Conf.PrivateKey(issid, counter)
691
	if err != nil {
692
		return nil, err
693
694
	}
	if sk == nil {
695
		return nil, errors.Errorf("unknown private key: %s", issid)
696
	}
697
	revsk, err := sk.RevocationKey()
698
	if err != nil {
699
		return nil, err
700
	}
701
702
	return revsk, nil
}
703

704
func (rs RevocationKeys) PublicKey(issid IssuerIdentifier, counter uint) (*revocation.PublicKey, error) {
705
	pk, err := rs.Conf.PublicKey(issid, counter)
706
	if err != nil {
707
		return nil, err
708
	}
709
710
	if pk == nil {
		return nil, errors.Errorf("unknown public key: %s-%d", issid, counter)
711
	}
712
713
714
	revpk, err := pk.RevocationKey()
	if err != nil {
		return nil, err
715
	}
716
	return revpk, nil
717
}
718
719
720
721
722
723
724
725
726
727
728

// Conversion methods to/from database structs, SQL table rows, gob

func (e *EventRecord) Event() *revocation.Event {
	return &revocation.Event{
		Index:      e.Index,
		E:          (*big.Int)(e.E),
		ParentHash: revocation.Hash(e.ParentHash),
	}
}

729
func (e *EventRecord) Convert(typ CredentialTypeIdentifier, pkcounter uint, event *revocation.Event) *EventRecord {
730
731
732
733
734
	*e = EventRecord{
		Index:      event.Index,
		E:          (*RevocationAttribute)(event.E),
		ParentHash: eventHash(event.ParentHash),
		CredType:   typ,
735
		PKCounter:  pkcounter,
736
737
738
739
740
741
	}
	return e
}

func (a *AccumulatorRecord) SignedAccumulator() *revocation.SignedAccumulator {
	return &revocation.SignedAccumulator{
742
743
		PKCounter: a.PKCounter,
		Data:      signed.Message(a.Data),
744
745
746
747
748
	}
}

func (a *AccumulatorRecord) Convert(typ CredentialTypeIdentifier, sacc *revocation.SignedAccumulator) *AccumulatorRecord {
	*a = AccumulatorRecord{
749
750
751
		Data:      signedMessage(sacc.Data),
		PKCounter: sacc.PKCounter,
		CredType:  typ,
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
	}
	return a
}

func (signedMessage) GormDataType(dialect gorm.Dialect) string {
	switch dialect.GetName() {
	case "postgres":
		return "bytea"
	case "mysql":
		return "blob"
	default:
		return ""
	}
}

// Value implements driver.Valuer, for SQL marshaling (to []byte).
func (i *RevocationAttribute) Value() (driver.Value, error) {
	return (*big.Int)(i).Bytes(), nil
}

// Scan implements sql.Scanner, for SQL unmarshaling (from a []byte).
func (i *RevocationAttribute) Scan(src interface{}) error {
	b, ok := src.([]byte)
	if !ok {
		return errors.New("cannot convert source: not a byte slice")
	}
	(*big.Int)(i).SetBytes(b)
	return nil
}

func (RevocationAttribute) GormDataType(dialect gorm.Dialect) string {
	switch dialect.GetName() {
	case "postgres":
		return "bytea"
	case "mysql":
		return "blob"
	default:
		return ""
	}
}

793
794
func (i *RevocationAttribute) MarshalCBOR() ([]byte, error) {
	return cbor.Marshal((*big.Int)(i), cbor.EncOptions{})
795
796
}

797
798
func (i *RevocationAttribute) UnmarshalCBOR(data []byte) error {
	return cbor.Unmarshal(data, (*big.Int)(i))
799
800
801
}

func (hash eventHash) Value() (driver.Value, error) {
802
	return []byte(hash), nil
803
804
805
806
807
808
809
}

func (hash *eventHash) Scan(src interface{}) error {
	s, ok := src.([]byte)
	if !ok {
		return errors.New("cannot convert source: not a []byte")
	}
810
811
	*hash = make([]byte, len(s))
	copy(*hash, s)
812
813
814
815
816
817
818
819
820
821
822
823
824
	return nil
}

func (eventHash) GormDataType(dialect gorm.Dialect) string {
	switch dialect.GetName() {
	case "postgres":
		return "bytea"
	case "mysql":
		return "blob"
	default:
		return ""
	}
}