revocation.go 23.4 KB
Newer Older
1 2 3
package irma

import (
4
	"database/sql/driver"
5 6 7
	"fmt"
	"time"

8
	"github.com/fxamacker/cbor"
9
	"github.com/go-errors/errors"
10
	"github.com/hashicorp/go-multierror"
11
	"github.com/jinzhu/gorm"
12
	"github.com/privacybydesign/gabi"
13
	"github.com/privacybydesign/gabi/big"
14
	"github.com/privacybydesign/gabi/revocation"
15
	"github.com/privacybydesign/gabi/signed"
16 17 18

	_ "github.com/jinzhu/gorm/dialects/mysql"
	_ "github.com/jinzhu/gorm/dialects/postgres"
19 20
)

21
type (
Sietse Ringers's avatar
Sietse Ringers committed
22 23 24
	// 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.
25 26 27 28 29 30 31 32 33
	RevocationStorage struct {
		conf     *Configuration
		db       revStorage
		memdb    memRevStorage
		sqlMode  bool
		settings map[CredentialTypeIdentifier]*RevocationSetting

		Keys   RevocationKeys
		client RevocationClient
34 35
	}

Sietse Ringers's avatar
Sietse Ringers committed
36
	// RevocationClient offers an HTTP client to the revocation server endpoints.
37 38 39 40
	RevocationClient struct {
		Conf *Configuration
	}

Sietse Ringers's avatar
Sietse Ringers committed
41 42
	// RevocationKeys contains helper functions for retrieving revocation private and public keys
	// from an irma.Configuration instance.
43 44 45 46
	RevocationKeys struct {
		Conf *Configuration
	}

Sietse Ringers's avatar
Sietse Ringers committed
47
	// RevocationSetting contains revocation settings for a given credential type.
48
	RevocationSetting struct {
49
		Mode                     RevocationMode `json:"mode" mapstructure:"mode"`
50
		PostURLs                 []string       `json:"post_urls" mapstructure:"post_urls"`
51
		ServerURL                string         `json:"server_url" mapstructure:"server_url"`
52
		MaxNonrevocationDuration uint           `json:"max_nonrev_duration" mapstructure:"max_nonrev_duration"` // in seconds, min 30
53

54 55
		// 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
56 57
		// guarantees lasts.
		updated time.Time
58 59
	}

Sietse Ringers's avatar
Sietse Ringers committed
60 61
	// RevocationMode specifies for a given credential type what revocation operations are
	// supported, and how the associated data is stored (SQL or memory).
62
	RevocationMode string
63
)
64

65
// Structs corresponding to SQL table rows, ending in Record
66
type (
67 68 69 70 71 72 73
	// 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

74
	AccumulatorRecord struct {
75 76
		CredType CredentialTypeIdentifier `gorm:"primary_key"`
		Data     signedMessage
77
		PKIndex  uint `gorm:"primary_key;auto_increment:false"`
78 79
	}

80
	EventRecord struct {
81 82
		Index      uint64                   `gorm:"primary_key;column:eventindex"`
		CredType   CredentialTypeIdentifier `gorm:"primary_key"`
83
		PKIndex    uint                     `gorm:"primary_key;auto_increment:false"`
84 85
		E          *RevocationAttribute
		ParentHash eventHash
86 87 88 89
	}

	// IssuanceRecord contains information generated during issuance, needed for later revocation.
	IssuanceRecord struct {
90
		Key        string                   `gorm:"primary_key;column:revocationkey"`
91
		CredType   CredentialTypeIdentifier `gorm:"primary_key"`
92
		PKIndex    uint
93
		Attr       *RevocationAttribute
94 95
		Issued     int64
		ValidUntil int64
96
		RevokedAt  int64 `json:",omitempty"` // 0 if not currently revoked
97
	}
98 99 100 101 102 103

	// TODO
	TimeRecord struct {
		Index      uint64
		Start, End int64
	}
104
)
105

106
const (
Sietse Ringers's avatar
Sietse Ringers committed
107 108
	// RevocationModeRequestor is the default revocation mode in which only RevocationRecord instances
	// are consumed for issuance or verification. Uses an in-memory store.
109
	RevocationModeRequestor RevocationMode = ""
Sietse Ringers's avatar
Sietse Ringers committed
110 111

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

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

126
	// revocationUpdateCount specifies how many revocation events are attached to session requests
Sietse Ringers's avatar
Sietse Ringers committed
127 128
	// for the client to update its revocation state.
	revocationUpdateCount = 5
129

130 131 132
	// 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
133
	// against a 'younger' accumulator.
134
	revocationMaxAccumulatorAge uint = 5 * 60
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, pkindex 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, pkindex)
180 181 182 183
		if err != nil {
			return err
		}
		var events []*EventRecord
184
		if err := tx.From(&events, "cred_type = ? and pk_index = ? and index >= ?", typ, pkindex, 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 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
func (*RevocationStorage) newUpdates(records []*AccumulatorRecord, events []*EventRecord) map[uint]*revocation.Update {
	accs := map[uint]*revocation.SignedAccumulator{}
	for _, r := range records {
		accs[r.PKIndex] = r.SignedAccumulator()
	}
	updates := make(map[uint]*revocation.Update, len(accs))
	for _, e := range events {
		i := e.PKIndex
		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())
	}
	return updates
}

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

253
func (rs *RevocationStorage) AddUpdate(typ CredentialTypeIdentifier, record *revocation.Update) error {
254
	return rs.addUpdate(rs.db, typ, record, false)
255
}
256

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

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

285
	s := rs.getSettings(typ)
286
	s.updated = time.Now()
287 288
	// POST record to listeners, if any, asynchroniously
	go rs.client.PostUpdate(typ, s.PostURLs, update)
289 290 291

	return nil
}
292

293 294 295
// Issuance records

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

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

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

312 313
// Revocation methods

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

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

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

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

363
// Accumulator methods
364

365 366
// accumulator retrieves, verifies and deserializes the accumulator of the given type and key.
func (rs *RevocationStorage) accumulator(tx revStorage, typ CredentialTypeIdentifier, pkindex uint) (
367 368 369
	*revocation.SignedAccumulator, *revocation.Accumulator, error,
) {
	var err error
370
	var sacc *revocation.SignedAccumulator
371
	if rs.sqlMode {
372
		record := &AccumulatorRecord{}
373
		if err = tx.Last(record, map[string]interface{}{"cred_type": typ, "pk_index": pkindex}); err != nil {
374
			if gorm.IsRecordNotFoundError(err) {
375
				return nil, nil, nil
376
			}
377
		}
378
		sacc = record.SignedAccumulator()
379
	} else {
380 381
		sacc = rs.memdb.SignedAccumulator(typ, pkindex)
		if sacc == nil {
382
			return nil, nil, nil
383
		}
384 385
	}

386
	pk, err := rs.Keys.PublicKey(typ.IssuerIdentifier(), sacc.PKIndex)
387
	if err != nil {
388
		return nil, nil, err
389
	}
390
	acc, err := sacc.UnmarshalVerify(pk)
391 392
	if err != nil {
		return nil, nil, err
393
	}
394
	return sacc, acc, nil
395 396
}

397 398 399
// Methods to update from remote revocation server

func (rs *RevocationStorage) UpdateDB(typ CredentialTypeIdentifier) error {
400
	updates, err := rs.client.FetchUpdateLatest(typ, revocationUpdateCount)
401
	if err != nil {
402
		return err
403
	}
404 405 406 407
	for _, u := range updates {
		if err = rs.AddUpdate(typ, u); err != nil {
			return err
		}
408 409 410 411
	}
	// bump updated even if no new records were added
	rs.getSettings(typ).updated = time.Now()
	return nil
412 413
}

Sietse Ringers's avatar
Sietse Ringers committed
414
func (rs *RevocationStorage) UpdateIfOld(typ CredentialTypeIdentifier) error {
415
	settings := rs.getSettings(typ)
416
	// update 10 seconds before the maximum, to stay below it
417
	if settings.updated.Before(time.Now().Add(time.Duration(-settings.MaxNonrevocationDuration+10) * time.Second)) {
418
		if err := rs.UpdateDB(typ); err != nil {
419 420 421 422 423 424
			return err
		}
	}
	return nil
}

425 426
// 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.
427 428 429 430 431 432 433 434 435
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")
	}

436
	// Just store it if we are the revocation server for this credential type
437 438
	settings := rs.getSettings(typ)
	if settings.Mode == RevocationModeServer {
439
		return rs.AddIssuanceRecord(rec)
440 441
	}

442
	// We have to send it, sign it first
443 444 445
	if settings.ServerURL == "" {
		return errors.New("cannot send issuance record: no server_url configured")
	}
446
	rsk, err := sk.RevocationKey()
447 448 449
	if err != nil {
		return err
	}
450
	return rs.client.PostIssuanceRecord(typ, rsk, rec, settings.ServerURL)
451 452
}

453 454
// Misscelaneous methods

455
func (rs *RevocationStorage) Load(debug bool, dbtype, connstr string, settings map[CredentialTypeIdentifier]*RevocationSetting) error {
456
	var t *CredentialTypeIdentifier
457

458 459
	for typ, s := range settings {
		switch s.Mode {
460 461 462 463
		case RevocationModeServer:
			if s.ServerURL != "" {
				return errors.New("server_url cannot be combined with server mode")
			}
464
			t = &typ
465 466 467
		case RevocationModeProxy:
			t = &typ
		case RevocationModeRequestor: // noop
468
		default:
469 470
			return errors.Errorf(`invalid revocation mode "%s" for %s (supported: "%s", "%s", "%s")`,
				s.Mode, typ, RevocationModeRequestor, RevocationModeServer, RevocationModeProxy)
471
		}
472
	}
473 474 475
	if t != nil && connstr == "" {
		return errors.Errorf("revocation mode for %s requires SQL database but no connection string given", *t)
	}
476 477 478 479 480 481 482

	if connstr == "" {
		Logger.Trace("Using memory revocation database")
		rs.memdb = newMemStorage()
		rs.sqlMode = false
	} else {
		Logger.Trace("Connecting to revocation SQL database")
483
		db, err := newSqlStorage(debug, dbtype, connstr)
484
		if err != nil {
485
			return err
486
		}
487 488
		rs.db = db
		rs.sqlMode = true
489
	}
490 491 492 493 494
	if settings != nil {
		rs.settings = settings
	} else {
		rs.settings = map[CredentialTypeIdentifier]*RevocationSetting{}
	}
495 496 497 498 499 500
	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)
		}
	}
501 502 503
	rs.client = RevocationClient{Conf: rs.conf}
	rs.Keys = RevocationKeys{Conf: rs.conf}
	return nil
504 505
}

506 507 508
func (rs *RevocationStorage) Close() error {
	if rs.db != nil {
		return rs.db.Close()
509 510 511 512
	}
	return nil
}

513
// SetRevocationUpdates retrieves the latest revocation records from the database, and attaches
514 515
// them to the request, for each credential type for which a nonrevocation proof is requested in
// b.Revocation.
516
func (rs *RevocationStorage) SetRevocationUpdates(b *BaseRequest) error {
517 518
	if len(b.Revocation) == 0 {
		return nil
519
	}
520
	var err error
521
	b.RevocationUpdates = make(map[CredentialTypeIdentifier]map[uint]*revocation.Update, len(b.Revocation))
522
	for _, credid := range b.Revocation {
523 524 525
		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
526
		if err = rs.UpdateIfOld(credid); err != nil {
527 528 529 530 531 532 533 534 535 536 537 538
			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
			}
539
		}
540
		b.RevocationUpdates[credid], err = rs.UpdateLatest(credid, revocationUpdateCount)
541 542 543
		if err != nil {
			return err
		}
544
	}
545 546 547 548 549 550
	return nil
}

func (rs *RevocationStorage) getSettings(typ CredentialTypeIdentifier) *RevocationSetting {
	if rs.settings[typ] == nil {
		rs.settings[typ] = &RevocationSetting{}
551
	}
552 553 554 555 556
	s := rs.settings[typ]
	if s.MaxNonrevocationDuration == 0 {
		s.MaxNonrevocationDuration = revocationMaxAccumulatorAge
	}
	return s
557 558
}

559
func (client RevocationClient) PostUpdate(typ CredentialTypeIdentifier, urls []string, update *revocation.Update) {
560
	transport := NewHTTPTransport("")
561
	transport.Binary = true
562
	for _, url := range urls {
563 564
		err := transport.Post(fmt.Sprintf("%s/revocation/update/%s", url, typ.String()), nil, update)
		if err != nil {
565 566
			Logger.Warn("error sending revocation update", err)
		}
567
	}
568 569
}

570 571 572 573 574
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
	}
575
	return NewHTTPTransport(url).Post(
576
		fmt.Sprintf("revocation/issuancerecord/%s/%d", typ, sk.Counter), nil, []byte(message),
577 578 579 580
	)
}

// FetchRevocationRecords gets revocation update messages from the revocation server, of the specified index and greater.
581 582 583 584 585 586 587
func (client RevocationClient) FetchUpdateFrom(typ CredentialTypeIdentifier, pkindex uint, index uint64) (*revocation.Update, error) {
	update := &revocation.Update{}
	return update, client.getMultiple(
		client.Conf.CredentialTypes[typ].RevocationServers,
		fmt.Sprintf("revocation/updatefrom/%s/%d/%d", typ, pkindex, index),
		&update,
	)
588
}
589

590 591 592 593 594 595 596
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,
	)
597 598
}

599
func (RevocationClient) getMultiple(urls []string, path string, dest interface{}) error {
600 601 602 603 604
	var (
		errs      multierror.Error
		transport = NewHTTPTransport("")
	)
	transport.Binary = true
605
	for _, url := range urls {
606
		transport.Server = url
607
		err := transport.Get(path, dest)
608
		if err == nil {
609
			return nil
610 611 612
		} else {
			errs.Errors = append(errs.Errors, err)
		}
613
	}
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
	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
630 631
}

632 633
func (rs RevocationKeys) PrivateKey(issid IssuerIdentifier, counter uint) (*revocation.PrivateKey, error) {
	sk, err := rs.Conf.PrivateKey(issid, int(counter))
634
	if err != nil {
635
		return nil, err
636 637
	}
	if sk == nil {
638
		return nil, errors.Errorf("unknown private key: %s", issid)
639
	}
640
	revsk, err := sk.RevocationKey()
641
	if err != nil {
642
		return nil, err
643
	}
644 645
	return revsk, nil
}
646

647 648
func (rs RevocationKeys) PublicKey(issid IssuerIdentifier, counter uint) (*revocation.PublicKey, error) {
	pk, err := rs.Conf.PublicKey(issid, int(counter))
649
	if err != nil {
650
		return nil, err
651
	}
652 653
	if pk == nil {
		return nil, errors.Errorf("unknown public key: %s-%d", issid, counter)
654
	}
655 656 657
	revpk, err := pk.RevocationKey()
	if err != nil {
		return nil, err
658
	}
659
	return revpk, nil
660
}
661 662 663 664 665 666 667 668 669 670 671

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

672
func (e *EventRecord) Convert(typ CredentialTypeIdentifier, pkindex uint, event *revocation.Event) *EventRecord {
673 674 675 676 677
	*e = EventRecord{
		Index:      event.Index,
		E:          (*RevocationAttribute)(event.E),
		ParentHash: eventHash(event.ParentHash),
		CredType:   typ,
678
		PKIndex:    pkindex,
679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
	}
	return e
}

func (a *AccumulatorRecord) SignedAccumulator() *revocation.SignedAccumulator {
	return &revocation.SignedAccumulator{
		PKIndex: a.PKIndex,
		Data:    signed.Message(a.Data),
	}
}

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

736 737
func (i *RevocationAttribute) MarshalCBOR() ([]byte, error) {
	return cbor.Marshal((*big.Int)(i), cbor.EncOptions{})
738 739
}

740 741
func (i *RevocationAttribute) UnmarshalCBOR(data []byte) error {
	return cbor.Unmarshal(data, (*big.Int)(i))
742 743 744
}

func (hash eventHash) Value() (driver.Value, error) {
745
	return []byte(hash), nil
746 747 748 749 750 751 752
}

func (hash *eventHash) Scan(src interface{}) error {
	s, ok := src.([]byte)
	if !ok {
		return errors.New("cannot convert source: not a []byte")
	}
753 754
	*hash = make([]byte, len(s))
	copy(*hash, s)
755 756 757 758 759 760 761 762 763 764 765 766 767
	return nil
}

func (eventHash) GormDataType(dialect gorm.Dialect) string {
	switch dialect.GetName() {
	case "postgres":
		return "bytea"
	case "mysql":
		return "blob"
	default:
		return ""
	}
}