revocation.go 28.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
	RevocationStorage struct {
		conf     *Configuration
29
		sqldb    sqlRevStorage
30 31 32 33 34 35
		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
		Issued     int64                    `gorm:"primary_key;auto_increment:false"`
95
		PKCounter  uint
96
		Attr       *RevocationAttribute
97
		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 141

	// DELETE issuance records of expired credential every so many minutes
	revocationDeleteIssuanceRecordsInterval uint64 = 300
142 143
)

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

156
	update, err := revocation.NewAccumulator(sk)
157 158 159
	if err != nil {
		return err
	}
160

161
	if err = rs.addUpdate(rs.sqldb, typ, update, true); err != nil {
162 163 164 165 166
		return err
	}
	return nil
}

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

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
	// Only requires SQL implementation
	var update *revocation.Update
181
	if err := rs.sqldb.Transaction(func(tx sqlRevStorage) 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.sqldb.Transaction(func(tx sqlRevStorage) 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.sqldb, typ, record, false)
263
}
264

265
func (rs *RevocationStorage) addUpdate(tx sqlRevStorage, 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) AddIssuanceRecord(r *IssuanceRecord) error {
304
	return rs.sqldb.Insert(r)
305 306
}

307 308 309 310 311
func (rs *RevocationStorage) IssuanceRecords(typ CredentialTypeIdentifier, key string, issued time.Time) ([]*IssuanceRecord, error) {
	where := map[string]interface{}{"cred_type": typ, "revocationkey": key}
	if !issued.IsZero() {
		where["Issued"] = issued.UnixNano()
	}
312
	var r []*IssuanceRecord
313
	err := rs.sqldb.Find(&r, where)
314 315
	if err != nil {
		return nil, err
316
	}
317 318 319 320
	if len(r) == 0 {
		return nil, gorm.ErrRecordNotFound
	}
	return r, nil
321
}
322

323 324
// Revocation methods

Sietse Ringers's avatar
Sietse Ringers committed
325 326
// 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,
327
// and updating the revocation database on disk.
328
func (rs *RevocationStorage) Revoke(typ CredentialTypeIdentifier, key string, issued time.Time) error {
Sietse Ringers's avatar
Sietse Ringers committed
329 330
	if rs.getSettings(typ).Mode != RevocationModeServer {
		return errors.Errorf("cannot revoke %s", typ)
331
	}
332
	return rs.sqldb.Transaction(func(tx sqlRevStorage) error {
333
		return rs.revoke(tx, typ, key, issued)
334 335 336
	})
}

337
func (rs *RevocationStorage) revoke(tx sqlRevStorage, typ CredentialTypeIdentifier, key string, issued time.Time) error {
338
	var err error
339
	issrecords, err := rs.IssuanceRecords(typ, key, issued)
340 341 342 343 344 345 346 347 348 349 350 351
	if err != nil {
		return err
	}

	// get all relevant accumulators and events from the database
	accs, events, err := rs.revokeReadRecords(tx, typ, issrecords)

	// For each issuance record, perform revocation, adding an Event and advancing the accumulator
	for _, issrecord := range issrecords {
		e := events[issrecord.PKCounter]
		newacc, event, err := rs.revokeCredential(tx, issrecord, accs[issrecord.PKCounter], e[len(e)-1])
		accs[issrecord.PKCounter] = newacc
352
		if err != nil {
353 354
			return err
		}
355 356 357 358 359 360 361 362
		events[issrecord.PKCounter] = append(e, event)
	}

	// Gather accumulators and update events per key counter into revocation updates,
	// and add them to the database
	for counter := range accs {
		sk, err := rs.Keys.PrivateKey(typ.IssuerIdentifier(), counter)
		if err != nil {
363 364
			return err
		}
365 366
		// exclude parent event from the events
		update, err := revocation.NewUpdate(sk, accs[counter], events[counter][1:])
367 368 369
		if err != nil {
			return err
		}
370 371 372 373 374 375
		if err = rs.addUpdate(tx, typ, update, false); err != nil {
			return err
		}
	}

	return nil
376 377
}

378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
func (rs *RevocationStorage) revokeReadRecords(
	tx sqlRevStorage,
	typ CredentialTypeIdentifier,
	issrecords []*IssuanceRecord,
) (map[uint]*revocation.Accumulator, map[uint][]*revocation.Event, error) {
	// gather all keys used in the issuance requests
	var keycounters []uint
	for _, issrecord := range issrecords {
		keycounters = append(keycounters, issrecord.PKCounter)
	}

	// get all relevant accumulators from the database
	var records []AccumulatorRecord
	if err := tx.Find(&records, "cred_type = ? and pk_counter in (?)", typ, keycounters); err != nil {
		return nil, nil, err
	}
	var eventrecords []EventRecord
	err := rs.sqldb.gorm.Table("event_records e").Find(&eventrecords,
		"e.eventindex = (?)", rs.sqldb.gorm.
			Table("event_records e2").
			Select("max(e2.eventindex)").
			Where("e2.cred_type = e.cred_type and e2.pk_counter = e.pk_counter").
			QueryExpr(),
	).Error
402
	if err != nil {
403
		return nil, nil, err
404
	}
405 406 407 408 409 410 411 412 413 414 415 416 417

	accs := map[uint]*revocation.Accumulator{}
	events := map[uint][]*revocation.Event{}
	for _, r := range records {
		sacc := r.SignedAccumulator()
		pk, err := rs.Keys.PublicKey(typ.IssuerIdentifier(), sacc.PKCounter)
		if err != nil {
			return nil, nil, err
		}
		accs[r.PKCounter], err = sacc.UnmarshalVerify(pk)
		if err != nil {
			return nil, nil, err
		}
418
	}
419 420
	for _, e := range eventrecords {
		events[e.PKCounter] = append(events[e.PKCounter], e.Event())
421
	}
422 423
	return accs, events, nil
}
424

425 426 427 428 429 430 431 432 433 434 435
func (rs *RevocationStorage) revokeCredential(
	tx sqlRevStorage,
	issrecord *IssuanceRecord,
	acc *revocation.Accumulator,
	parent *revocation.Event,
) (*revocation.Accumulator, *revocation.Event, error) {
	issrecord.RevokedAt = time.Now().UnixNano()
	if err := tx.Save(&issrecord); err != nil {
		return nil, nil, err
	}
	sk, err := rs.Keys.PrivateKey(issrecord.CredType.IssuerIdentifier(), issrecord.PKCounter)
436
	if err != nil {
437
		return nil, nil, err
438
	}
439 440 441
	newacc, event, err := acc.Remove(sk, (*big.Int)(issrecord.Attr), parent)
	if err != nil {
		return nil, nil, err
442
	}
443
	return newacc, event, nil
444 445
}

446
// Accumulator methods
447

448
func (rs *RevocationStorage) Accumulator(typ CredentialTypeIdentifier, pkcounter uint) (
449
	*revocation.SignedAccumulator, error,
450
) {
451
	return rs.accumulator(rs.sqldb, typ, pkcounter)
452 453
}

454
// accumulator retrieves, verifies and deserializes the accumulator of the given type and key.
455
func (rs *RevocationStorage) accumulator(tx sqlRevStorage, typ CredentialTypeIdentifier, pkcounter uint) (
456
	*revocation.SignedAccumulator, error,
457 458
) {
	var err error
459
	var sacc *revocation.SignedAccumulator
460
	if rs.sqlMode {
461
		record := &AccumulatorRecord{}
462
		if err = tx.Last(record, map[string]interface{}{"cred_type": typ, "pk_counter": pkcounter}); err != nil {
463
			if gorm.IsRecordNotFoundError(err) {
464
				return nil, nil
465
			}
466
		}
467
		sacc = record.SignedAccumulator()
468
	} else {
469
		sacc = rs.memdb.SignedAccumulator(typ, pkcounter)
470
		if sacc == nil {
471
			return nil, nil
472
		}
473 474
	}

475
	pk, err := rs.Keys.PublicKey(typ.IssuerIdentifier(), sacc.PKCounter)
476
	if err != nil {
477
		return nil, err
478
	}
479
	_, err = sacc.UnmarshalVerify(pk)
480
	if err != nil {
481
		return nil, err
482
	}
483
	return sacc, nil
484 485
}

486 487 488
// Methods to update from remote revocation server

func (rs *RevocationStorage) UpdateDB(typ CredentialTypeIdentifier) error {
489
	updates, err := rs.client.FetchUpdateLatest(typ, RevocationDefaultUpdateEventCount)
490
	if err != nil {
491
		return err
492
	}
493 494 495 496
	for _, u := range updates {
		if err = rs.AddUpdate(typ, u); err != nil {
			return err
		}
497 498 499 500
	}
	// bump updated even if no new records were added
	rs.getSettings(typ).updated = time.Now()
	return nil
501 502
}

Sietse Ringers's avatar
Sietse Ringers committed
503
func (rs *RevocationStorage) UpdateIfOld(typ CredentialTypeIdentifier) error {
504
	settings := rs.getSettings(typ)
505
	// update 10 seconds before the maximum, to stay below it
506
	if settings.updated.Before(time.Now().Add(time.Duration(-settings.Tolerance+10) * time.Second)) {
507
		Logger.WithField("credtype", typ).Tracef("fetching revocation updates")
508
		if err := rs.UpdateDB(typ); err != nil {
509 510 511 512 513 514
			return err
		}
	}
	return nil
}

515 516
// 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.
517 518 519 520 521
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")
	}
522
	if !credtype.RevocationSupported() {
523 524 525
		return errors.New("cannot save issuance record: credential type does not support revocation")
	}

526
	// Just store it if we are the revocation server for this credential type
527 528
	settings := rs.getSettings(typ)
	if settings.Mode == RevocationModeServer {
529
		return rs.AddIssuanceRecord(rec)
530 531
	}

532
	// We have to send it, sign it first
533
	if settings.RevocationServerURL == "" {
534 535
		return errors.New("cannot send issuance record: no server_url configured")
	}
536
	rsk, err := sk.RevocationKey()
537 538 539
	if err != nil {
		return err
	}
540
	return rs.client.PostIssuanceRecord(typ, rsk, rec, settings.RevocationServerURL)
541 542
}

543 544
// Misscelaneous methods

545
func (rs *RevocationStorage) updateAccumulatorTimes(types []CredentialTypeIdentifier) error {
546
	return rs.sqldb.Transaction(func(tx sqlRevStorage) error {
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
		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
			}
575 576 577 578 579

			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})
580 581 582 583 584
		}
		return nil
	})
}

585
func (rs *RevocationStorage) Load(debug bool, dbtype, connstr string, settings map[CredentialTypeIdentifier]*RevocationSetting) error {
586
	var t *CredentialTypeIdentifier
587
	var ourtypes []CredentialTypeIdentifier
588 589
	for typ, s := range settings {
		switch s.Mode {
590
		case RevocationModeServer:
591
			if s.RevocationServerURL != "" {
592 593
				return errors.New("server_url cannot be combined with server mode")
			}
594
			ourtypes = append(ourtypes, typ)
595
			t = &typ
596 597 598
		case RevocationModeProxy:
			t = &typ
		case RevocationModeRequestor: // noop
599 600
		case revocationModeRequestor:
			s.Mode = RevocationModeRequestor
601
		default:
602 603
			return errors.Errorf(`invalid revocation mode "%s" for %s (supported: "%s" (or empty string), "%s", "%s")`,
				s.Mode, typ, revocationModeRequestor, RevocationModeServer, RevocationModeProxy)
604
		}
605
	}
606 607 608
	if t != nil && connstr == "" {
		return errors.Errorf("revocation mode for %s requires SQL database but no connection string given", *t)
	}
609

610 611 612 613 614 615 616 617
	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)
			}
		})
	}
618 619 620 621
	rs.conf.Scheduler.Every(revocationDeleteIssuanceRecordsInterval).Minutes().Do(func() {
		if !rs.sqlMode {
			return
		}
622
		if err := rs.sqldb.Delete(IssuanceRecord{}, "valid_until < ?", time.Now().UnixNano()); err != nil {
623 624 625 626
			err = errors.WrapPrefix(err, "failed to delete expired issuance records", 0)
			raven.CaptureError(err, nil)
		}
	})
627

628 629 630 631 632 633
	if connstr == "" {
		Logger.Trace("Using memory revocation database")
		rs.memdb = newMemStorage()
		rs.sqlMode = false
	} else {
		Logger.Trace("Connecting to revocation SQL database")
634
		db, err := newSqlStorage(debug, dbtype, connstr)
635
		if err != nil {
636
			return err
637
		}
638
		rs.sqldb = db
639
		rs.sqlMode = true
640
	}
641 642 643 644 645
	if settings != nil {
		rs.settings = settings
	} else {
		rs.settings = map[CredentialTypeIdentifier]*RevocationSetting{}
	}
646
	for id, settings := range rs.settings {
647
		if settings.Tolerance != 0 && settings.Tolerance < 30 {
648
			return errors.Errorf("max_nonrev_duration setting for %s must be at least 30 seconds, was %d",
649
				id, settings.Tolerance)
650 651
		}
	}
652 653 654
	rs.client = RevocationClient{Conf: rs.conf}
	rs.Keys = RevocationKeys{Conf: rs.conf}
	return nil
655 656
}

657
func (rs *RevocationStorage) Close() error {
658
	return rs.sqldb.Close()
659 660
}

661
// SetRevocationUpdates retrieves the latest revocation records from the database, and attaches
662 663
// them to the request, for each credential type for which a nonrevocation proof is requested in
// b.Revocation.
664
func (rs *RevocationStorage) SetRevocationUpdates(b *BaseRequest) error {
665 666
	if len(b.Revocation) == 0 {
		return nil
667
	}
668
	var err error
669
	b.RevocationUpdates = make(map[CredentialTypeIdentifier]map[uint]*revocation.Update, len(b.Revocation))
670
	for _, credid := range b.Revocation {
671
		if !rs.conf.CredentialTypes[credid].RevocationSupported() {
672 673
			return errors.Errorf("cannot request nonrevocation proof for %s: revocation not enabled in scheme")
		}
Sietse Ringers's avatar
Sietse Ringers committed
674
		if err = rs.UpdateIfOld(credid); err != nil {
675 676 677 678 679 680 681 682 683 684 685 686
			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
			}
687
		}
688
		b.RevocationUpdates[credid], err = rs.UpdateLatest(credid, RevocationDefaultUpdateEventCount)
689 690 691
		if err != nil {
			return err
		}
692
	}
693 694 695 696 697 698
	return nil
}

func (rs *RevocationStorage) getSettings(typ CredentialTypeIdentifier) *RevocationSetting {
	if rs.settings[typ] == nil {
		rs.settings[typ] = &RevocationSetting{}
699
	}
700
	s := rs.settings[typ]
701 702
	if s.Tolerance == 0 {
		s.Tolerance = revocationDefaultTolerance
703 704
	}
	return s
705 706
}

707
func (client RevocationClient) PostUpdate(typ CredentialTypeIdentifier, urls []string, update *revocation.Update) {
708
	transport := NewHTTPTransport("")
709
	transport.Binary = true
710
	for _, url := range urls {
711 712
		err := transport.Post(fmt.Sprintf("%s/revocation/update/%s", url, typ.String()), nil, update)
		if err != nil {
713 714
			Logger.Warn("error sending revocation update", err)
		}
715
	}
716 717
}

718 719 720 721 722
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
	}
723
	return NewHTTPTransport(url).Post(
724
		fmt.Sprintf("revocation/issuancerecord/%s/%d", typ, sk.Counter), nil, []byte(message),
725 726 727 728
	)
}

// FetchRevocationRecords gets revocation update messages from the revocation server, of the specified index and greater.
729
func (client RevocationClient) FetchUpdateFrom(typ CredentialTypeIdentifier, pkcounter uint, index uint64) (*revocation.Update, error) {
730 731 732
	update := &revocation.Update{}
	return update, client.getMultiple(
		client.Conf.CredentialTypes[typ].RevocationServers,
733
		fmt.Sprintf("revocation/updatefrom/%s/%d/%d", typ, pkcounter, index),
734 735
		&update,
	)
736
}
737

738 739 740 741 742 743 744
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,
	)
745 746
}

747
func (RevocationClient) getMultiple(urls []string, path string, dest interface{}) error {
748 749 750 751 752
	var (
		errs      multierror.Error
		transport = NewHTTPTransport("")
	)
	transport.Binary = true
753
	for _, url := range urls {
754
		transport.Server = url
755
		err := transport.Get(path, dest)
756
		if err == nil {
757
			return nil
758 759 760
		} else {
			errs.Errors = append(errs.Errors, err)
		}
761
	}
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
	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
778 779
}

780
func (rs RevocationKeys) PrivateKey(issid IssuerIdentifier, counter uint) (*revocation.PrivateKey, error) {
781
	sk, err := rs.Conf.PrivateKey(issid, counter)
782
	if err != nil {
783
		return nil, err
784 785
	}
	if sk == nil {
786
		return nil, errors.Errorf("unknown private key: %s", issid)