revocation.go 25.5 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.Exists(typ, sk.Counter)
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
}

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

169 170 171
// Revocation update message methods

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

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

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

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

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

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

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

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

	return nil
}
296

297 298 299
// Issuance records

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

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

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

316 317
// Revocation methods

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

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

344
func (rs *RevocationStorage) revokeAttr(tx revStorage, typ CredentialTypeIdentifier, sk *revocation.PrivateKey, e *RevocationAttribute) error {
345
	sacc, err := rs.accumulator(tx, typ, sk.Counter)
346 347 348
	if err != nil {
		return err
	}
349
	if sacc == nil {
350 351
		return errors.Errorf("cannot revoke for type %s, not enabled yet", typ)
	}
352
	cur := sacc.Accumulator
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
func (rs *RevocationStorage) Accumulator(typ CredentialTypeIdentifier, pkcounter uint) (
371
	*revocation.SignedAccumulator, error,
372 373 374 375
) {
	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
	*revocation.SignedAccumulator, error,
379 380
) {
	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
387
			}
388
		}
389
		sacc = record.SignedAccumulator()
390
	} else {
391
		sacc = rs.memdb.SignedAccumulator(typ, pkcounter)
392
		if sacc == nil {
393
			return nil, nil
394
		}
395 396
	}

397
	pk, err := rs.Keys.PublicKey(typ.IssuerIdentifier(), sacc.PKCounter)
398
	if err != nil {
399
		return nil, err
400
	}
401
	_, err = sacc.UnmarshalVerify(pk)
402
	if err != nil {
403
		return nil, err
404
	}
405
	return sacc, 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
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
			}
497 498 499 500 501

			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})
502 503 504 505 506
		}
		return nil
	})
}

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

530 531 532 533 534 535 536 537 538
	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)
			}
		})
	}

539 540 541 542 543 544
	if connstr == "" {
		Logger.Trace("Using memory revocation database")
		rs.memdb = newMemStorage()
		rs.sqlMode = false
	} else {
		Logger.Trace("Connecting to revocation SQL database")
545
		db, err := newSqlStorage(debug, dbtype, connstr)
546
		if err != nil {
547
			return err
548
		}
549 550
		rs.db = db
		rs.sqlMode = true
551
	}
552 553 554 555 556
	if settings != nil {
		rs.settings = settings
	} else {
		rs.settings = map[CredentialTypeIdentifier]*RevocationSetting{}
	}
557 558 559 560 561 562
	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)
		}
	}
563 564 565
	rs.client = RevocationClient{Conf: rs.conf}
	rs.Keys = RevocationKeys{Conf: rs.conf}
	return nil
566 567
}

568 569 570
func (rs *RevocationStorage) Close() error {
	if rs.db != nil {
		return rs.db.Close()
571 572 573 574
	}
	return nil
}

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

func (rs *RevocationStorage) getSettings(typ CredentialTypeIdentifier) *RevocationSetting {
	if rs.settings[typ] == nil {
		rs.settings[typ] = &RevocationSetting{}
613
	}
614 615 616 617 618
	s := rs.settings[typ]
	if s.MaxNonrevocationDuration == 0 {
		s.MaxNonrevocationDuration = revocationMaxAccumulatorAge
	}
	return s
619 620
}

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

632 633 634 635 636
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
	}
637
	return NewHTTPTransport(url).Post(
638
		fmt.Sprintf("revocation/issuancerecord/%s/%d", typ, sk.Counter), nil, []byte(message),
639 640 641 642
	)
}

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

652 653 654 655 656 657 658
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,
	)
659 660
}

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

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

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

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

734
func (e *EventRecord) Convert(typ CredentialTypeIdentifier, pkcounter uint, event *revocation.Event) *EventRecord {
735 736 737 738 739
	*e = EventRecord{
		Index:      event.Index,
		E:          (*RevocationAttribute)(event.E),
		ParentHash: eventHash(event.ParentHash),
		CredType:   typ,
740
		PKCounter:  pkcounter,
741 742 743 744 745 746
	}
	return e
}

func (a *AccumulatorRecord) SignedAccumulator() *revocation.SignedAccumulator {
	return &revocation.SignedAccumulator{
747 748
		PKCounter: a.PKCounter,
		Data:      signed.Message(a.Data),
749 750 751 752 753
	}
}

func (a *AccumulatorRecord) Convert(typ CredentialTypeIdentifier, sacc *revocation.SignedAccumulator) *AccumulatorRecord {
	*a = AccumulatorRecord{
754 755 756
		Data:      signedMessage(sacc.Data),
		PKCounter: sacc.PKCounter,
		CredType:  typ,
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 793 794 795 796 797
	}
	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 ""
	}
}

798 799
func (i *RevocationAttribute) MarshalCBOR() ([]byte, error) {
	return cbor.Marshal((*big.Int)(i), cbor.EncOptions{})
800 801
}