revocation.go 25.4 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
	sacc, err := rs.accumulator(tx, typ, sk.Counter)
347 348 349
	if err != nil {
		return err
	}
350
	if sacc == nil {
351 352
		return errors.Errorf("cannot revoke for type %s, not enabled yet", typ)
	}
353
	cur := sacc.Accumulator
354
	var parent EventRecord
355
	if err = rs.db.Last(&parent, map[string]interface{}{"cred_type": typ, "pk_counter": sk.Counter}); err != nil {
356 357
		return err
	}
358

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

369
// Accumulator methods
370

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

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

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

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

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

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

438 439
// 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.
440 441 442 443 444 445 446 447 448
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")
	}

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

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

466 467
// Misscelaneous methods

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
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
			}
498 499 500 501 502

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

803 804
func (i *RevocationAttribute) UnmarshalCBOR(data []byte) error {
	return cbor.Unmarshal(data, (*big.Int)(i))