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 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) {
166 167
	// only requires sql implementation
	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
// 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 301
// Issuance records

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

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

313 314
// Revocation methods

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

323 324
	return rs.db.Transaction(func(tx revStorage) error {
		var err error
325 326
		issrecord, err := rs.IssuanceRecord(typ, key)
		if err != nil {
327 328
			return err
		}
329 330
		issrecord.RevokedAt = time.Now().UnixNano()
		if err = tx.Save(&issrecord); err != nil {
331 332
			return err
		}
333
		sk, err := rs.Keys.PrivateKey(typ.IssuerIdentifier(), issrecord.PKCounter)
334 335 336
		if err != nil {
			return err
		}
337
		return rs.revokeAttr(tx, typ, sk, issrecord.Attr)
338 339 340
	})
}

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

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

365
// Accumulator methods
366

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

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

394
	pk, err := rs.Keys.PublicKey(typ.IssuerIdentifier(), sacc.PKCounter)
395
	if err != nil {
396
		return nil, err
397
	}
398
	_, err = sacc.UnmarshalVerify(pk)
399
	if err != nil {
400
		return nil, err
401
	}
402
	return sacc, nil
403 404
}

405 406 407
// Methods to update from remote revocation server

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

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

434 435
// 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.
436 437 438 439 440 441 442 443 444
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")
	}

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

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

462 463
// Misscelaneous methods

464 465 466 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
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
			}
494 495 496 497 498

			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})
499 500 501 502 503
		}
		return nil
	})
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

func (a *AccumulatorRecord) Convert(typ CredentialTypeIdentifier, sacc *revocation.SignedAccumulator) *AccumulatorRecord {
	*a = AccumulatorRecord{
753 754 755
		Data:      signedMessage(sacc.Data),
		PKCounter: sacc.PKCounter,
		CredType:  typ,
756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796
	}
	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 ""
	}
}

797 798
func (i *RevocationAttribute) MarshalCBOR() ([]byte, error) {
	return cbor.Marshal((*big.Int)(i), cbor.EncOptions{})
Sietse Ringers's avatar