revocation.go 25.7 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 52 53 54
		Mode                RevocationMode `json:"mode" mapstructure:"mode"`
		PostURLs            []string       `json:"post_urls" mapstructure:"post_urls"`
		RevocationServerURL string         `json:"revocation_server_url" mapstructure:"revocation_server_url"`
		Tolerance           uint           `json:"tolerance" mapstructure:"tolerance"` // 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 = ""
106
	revocationModeRequestor RevocationMode = "requestor" // synonym for RevocationModeRequestor
Sietse Ringers's avatar
Sietse Ringers committed
107 108

	// RevocationModeProxy indicates that this server
109 110
	// (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
111
	// RevocationSetting struct.
112
	// Requires a SQL server to store and retrieve update messages from.
Sietse Ringers's avatar
Sietse Ringers committed
113 114
	RevocationModeProxy RevocationMode = "proxy"

115
	// RevocationModeServer indicates that this is a revocation server for a credential type.
Sietse Ringers's avatar
Sietse Ringers committed
116 117 118
	// 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
119
	// private key to be accessible, in order to revoke and to sign new revocation update messages.
120
	// In addition this mode exposes the same endpoints as RevocationModeProxy.
Sietse Ringers's avatar
Sietse Ringers committed
121 122
	RevocationModeServer RevocationMode = "server"

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

127 128 129 130 131 132 133 134
	// RevocationRequestorUpdateInterval is the time period in minutes for requestor servers
	// updating their revocation state at th RA.
	RevocationRequestorUpdateInterval uint64 = 5

	// revocationDefaultTolerance is the default tolerance in seconds: nonrevocation should be proved
	// by clients up to maximally this amount of seconds ago at verification time. If not, the
	// server will report the time up until nonrevocation of the attribute is guaranteed to the requestor.
	revocationDefaultTolerance uint = 5 * 60
135 136 137

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

Sietse Ringers's avatar
Sietse Ringers committed
141 142 143
// 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.
144
func (rs *RevocationStorage) EnableRevocation(typ CredentialTypeIdentifier, sk *revocation.PrivateKey) error {
145
	enabled, err := rs.Exists(typ, sk.Counter)
146 147 148
	if err != nil {
		return err
	}
149 150
	if enabled {
		return errors.New("revocation already enabled")
151 152
	}

153
	update, err := revocation.NewAccumulator(sk)
154 155 156
	if err != nil {
		return err
	}
157

158
	if err = rs.addUpdate(rs.db, typ, update, true); err != nil {
159 160 161 162 163
		return err
	}
	return nil
}

164
// Exists returns whether or not an accumulator exists in the database for the given credential type.
165
func (rs *RevocationStorage) Exists(typ CredentialTypeIdentifier, counter uint) (bool, error) {
Sietse Ringers's avatar
Sietse Ringers committed
166
	if rs.sqlMode {
167
		return rs.db.Exists((*AccumulatorRecord)(nil), map[string]interface{}{"cred_type": typ, "pk_counter": counter})
Sietse Ringers's avatar
Sietse Ringers committed
168 169 170 171 172
	} else {
		return rs.memdb.HasRecords(typ), nil
	}
}

173 174 175
// Revocation update message methods

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

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

224 225 226
func (*RevocationStorage) newUpdates(records []*AccumulatorRecord, events []*EventRecord) map[uint]*revocation.Update {
	accs := map[uint]*revocation.SignedAccumulator{}
	for _, r := range records {
227
		accs[r.PKCounter] = r.SignedAccumulator()
228 229 230
	}
	updates := make(map[uint]*revocation.Update, len(accs))
	for _, e := range events {
231
		i := e.PKCounter
232 233 234 235 236 237 238 239 240 241
		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())
	}
242 243 244 245 246
	for _, update := range updates {
		sort.Slice(update.Events, func(i, j int) bool {
			return update.Events[i].Index < update.Events[j].Index
		})
	}
247 248 249 250
	return updates
}

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

261
func (rs *RevocationStorage) AddUpdate(typ CredentialTypeIdentifier, record *revocation.Update) error {
262
	return rs.addUpdate(rs.db, typ, record, false)
263
}
264

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

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

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

	return nil
}
300

301 302 303
// Issuance records

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

307 308
func (rs *RevocationStorage) AddIssuanceRecord(r *IssuanceRecord) error {
	return rs.db.Insert(r)
309 310
}

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

320 321
// Revocation methods

Sietse Ringers's avatar
Sietse Ringers committed
322 323
// 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,
324
// and updating the revocation database on disk.
325
func (rs *RevocationStorage) Revoke(typ CredentialTypeIdentifier, key string) error {
Sietse Ringers's avatar
Sietse Ringers committed
326 327
	if rs.getSettings(typ).Mode != RevocationModeServer {
		return errors.Errorf("cannot revoke %s", typ)
328
	}
329

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

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

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

372
// Accumulator methods
373

374
func (rs *RevocationStorage) Accumulator(typ CredentialTypeIdentifier, pkcounter uint) (
375
	*revocation.SignedAccumulator, error,
376 377 378 379
) {
	return rs.accumulator(rs.db, typ, pkcounter)
}

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

401
	pk, err := rs.Keys.PublicKey(typ.IssuerIdentifier(), sacc.PKCounter)
402
	if err != nil {
403
		return nil, err
404
	}
405
	_, err = sacc.UnmarshalVerify(pk)
406
	if err != nil {
407
		return nil, err
408
	}
409
	return sacc, nil
410 411
}

412 413 414
// Methods to update from remote revocation server

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

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

441 442
// 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.
443 444 445 446 447 448 449 450 451
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")
	}

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

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

469 470
// Misscelaneous methods

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
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
			}
501 502 503 504 505

			s := rs.getSettings(r.CredType)
			s.updated = time.Now()
			// POST record to listeners, if any, asynchroniously
			go rs.client.PostUpdate(r.CredType, s.PostURLs, &revocation.Update{SignedAccumulator: sacc})
506 507 508 509 510
		}
		return nil
	})
}

511
func (rs *RevocationStorage) Load(debug bool, dbtype, connstr string, settings map[CredentialTypeIdentifier]*RevocationSetting) error {
512
	var t *CredentialTypeIdentifier
513
	var ourtypes []CredentialTypeIdentifier
514 515
	for typ, s := range settings {
		switch s.Mode {
516
		case RevocationModeServer:
517
			if s.RevocationServerURL != "" {
518 519
				return errors.New("server_url cannot be combined with server mode")
			}
520
			ourtypes = append(ourtypes, typ)
521
			t = &typ
522 523 524
		case RevocationModeProxy:
			t = &typ
		case RevocationModeRequestor: // noop
525 526
		case revocationModeRequestor:
			s.Mode = RevocationModeRequestor
527
		default:
528 529
			return errors.Errorf(`invalid revocation mode "%s" for %s (supported: "%s" (or empty string), "%s", "%s")`,
				s.Mode, typ, revocationModeRequestor, RevocationModeServer, RevocationModeProxy)
530
		}
531
	}
532 533 534
	if t != nil && connstr == "" {
		return errors.Errorf("revocation mode for %s requires SQL database but no connection string given", *t)
	}
535

536 537 538 539 540 541 542 543 544
	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)
			}
		})
	}

545 546 547 548 549 550
	if connstr == "" {
		Logger.Trace("Using memory revocation database")
		rs.memdb = newMemStorage()
		rs.sqlMode = false
	} else {
		Logger.Trace("Connecting to revocation SQL database")
551
		db, err := newSqlStorage(debug, dbtype, connstr)
552
		if err != nil {
553
			return err
554
		}
555 556
		rs.db = db
		rs.sqlMode = true
557
	}
558 559 560 561 562
	if settings != nil {
		rs.settings = settings
	} else {
		rs.settings = map[CredentialTypeIdentifier]*RevocationSetting{}
	}
563
	for id, settings := range rs.settings {
564
		if settings.Tolerance != 0 && settings.Tolerance < 30 {
565
			return errors.Errorf("max_nonrev_duration setting for %s must be at least 30 seconds, was %d",
566
				id, settings.Tolerance)
567 568
		}
	}
569 570 571
	rs.client = RevocationClient{Conf: rs.conf}
	rs.Keys = RevocationKeys{Conf: rs.conf}
	return nil
572 573
}

574 575 576
func (rs *RevocationStorage) Close() error {
	if rs.db != nil {
		return rs.db.Close()
577 578 579 580
	}
	return nil
}

581
// SetRevocationUpdates retrieves the latest revocation records from the database, and attaches
582 583
// them to the request, for each credential type for which a nonrevocation proof is requested in
// b.Revocation.
584
func (rs *RevocationStorage) SetRevocationUpdates(b *BaseRequest) error {
585 586
	if len(b.Revocation) == 0 {
		return nil
587
	}
588
	var err error
589
	b.RevocationUpdates = make(map[CredentialTypeIdentifier]map[uint]*revocation.Update, len(b.Revocation))
590
	for _, credid := range b.Revocation {
591 592 593
		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
594
		if err = rs.UpdateIfOld(credid); err != nil {
595 596 597 598 599 600 601 602 603 604 605 606
			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
			}
607
		}
608
		b.RevocationUpdates[credid], err = rs.UpdateLatest(credid, RevocationDefaultUpdateEventCount)
609 610 611
		if err != nil {
			return err
		}
612
	}
613 614 615 616 617 618
	return nil
}

func (rs *RevocationStorage) getSettings(typ CredentialTypeIdentifier) *RevocationSetting {
	if rs.settings[typ] == nil {
		rs.settings[typ] = &RevocationSetting{}
619
	}
620
	s := rs.settings[typ]
621 622
	if s.Tolerance == 0 {
		s.Tolerance = revocationDefaultTolerance
623 624
	}
	return s
625 626
}

627
func (client RevocationClient) PostUpdate(typ CredentialTypeIdentifier, urls []string, update *revocation.Update) {
628
	transport := NewHTTPTransport("")
629
	transport.Binary = true
630
	for _, url := range urls {
631 632
		err := transport.Post(fmt.Sprintf("%s/revocation/update/%s", url, typ.String()), nil, update)
		if err != nil {
633 634
			Logger.Warn("error sending revocation update", err)
		}
635
	}
636 637
}

638 639 640 641 642
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
	}
643
	return NewHTTPTransport(url).Post(
644
		fmt.Sprintf("revocation/issuancerecord/%s/%d", typ, sk.Counter), nil, []byte(message),
645 646 647 648
	)
}

// FetchRevocationRecords gets revocation update messages from the revocation server, of the specified index and greater.
649
func (client RevocationClient) FetchUpdateFrom(typ CredentialTypeIdentifier, pkcounter uint, index uint64) (*revocation.Update, error) {
650 651 652
	update := &revocation.Update{}
	return update, client.getMultiple(
		client.Conf.CredentialTypes[typ].RevocationServers,
653
		fmt.Sprintf("revocation/updatefrom/%s/%d/%d", typ, pkcounter, index),
654 655
		&update,
	)
656
}
657

658 659 660 661 662 663 664
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,
	)
665 666
}

667
func (RevocationClient) getMultiple(urls []string, path string, dest interface{}) error {
668 669 670 671 672
	var (
		errs      multierror.Error
		transport = NewHTTPTransport("")
	)
	transport.Binary = true
673
	for _, url := range urls {
674
		transport.Server = url
675
		err := transport.Get(path, dest)
676
		if err == nil {
677
			return nil
678 679 680
		} else {
			errs.Errors = append(errs.Errors, err)
		}
681
	}
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697
	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
698 699
}

700
func (rs RevocationKeys) PrivateKey(issid IssuerIdentifier, counter uint) (*revocation.PrivateKey, error) {
701
	sk, err := rs.Conf.PrivateKey(issid, counter)
702
	if err != nil {
703
		return nil, err
704 705
	}
	if sk == nil {
706
		return nil, errors.Errorf("unknown private key: %s", issid)
707
	}
708
	revsk, err := sk.RevocationKey()
709
	if err != nil {
710
		return nil, err
711
	}
712 713
	return revsk, nil
}
714

715
func (rs RevocationKeys) PublicKey(issid IssuerIdentifier, counter uint) (*revocation.PublicKey, error) {
716
	pk, err := rs.Conf.PublicKey(issid, counter)
717
	if err != nil {
718
		return nil, err
719
	}
720 721
	if pk == nil {
		return nil, errors.Errorf("unknown public key: %s-%d", issid, counter)
722
	}
723 724 725
	revpk, err := pk.RevocationKey()
	if err != nil {
		return nil, err
726
	}
727
	return revpk, nil
728
}
729 730 731 732 733 734 735 736 737 738 739

// 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),
	}
}

740
func (e *EventRecord) Convert(typ CredentialTypeIdentifier, pkcounter uint, event *revocation.Event) *EventRecord {
741 742 743 744 745
	*e = EventRecord{
		Index:      event.Index,
		E:          (*RevocationAttribute)(event.E),
		ParentHash: eventHash(event.ParentHash),
		CredType:   typ,
746
		PKCounter:  pkcounter,
747 748 749 750 751 752
	}
	return e
}

func (a *AccumulatorRecord) SignedAccumulator() *revocation.SignedAccumulator {
	return &revocation.SignedAccumulator{
753 754
		PKCounter: a.PKCounter,
		Data:      signed.Message(a.Data),
755 756 757 758 759
	}
}

func (a *AccumulatorRecord) Convert(typ CredentialTypeIdentifier, sacc *revocation.SignedAccumulator) *AccumulatorRecord {
	*a = AccumulatorRecord{
760 761 762
		Data:      signedMessage(sacc.Data),
		PKCounter: sacc.PKCounter,
		CredType:  typ,
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 793 794 795 796 797 798 799