revocation.go 23.9 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/go-errors/errors"
11
	"github.com/hashicorp/go-multierror"
12
	"github.com/jinzhu/gorm"
13
	"github.com/privacybydesign/gabi"
14
	"github.com/privacybydesign/gabi/big"
15
	"github.com/privacybydesign/gabi/revocation"
16
	"github.com/privacybydesign/gabi/signed"
17 18 19

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

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

		Keys   RevocationKeys
		client RevocationClient
35 36
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

131 132 133
	// 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
134
	// against a 'younger' accumulator.
135
	revocationMaxAccumulatorAge uint = 5 * 60
136 137
)

Sietse Ringers's avatar
Sietse Ringers committed
138 139 140
// 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.
141
func (rs *RevocationStorage) EnableRevocation(typ CredentialTypeIdentifier, sk *revocation.PrivateKey) error {
142
	enabled, err := rs.RevocationEnabled(typ)
143 144 145
	if err != nil {
		return err
	}
146 147
	if enabled {
		return errors.New("revocation already enabled")
148 149
	}

150
	update, err := revocation.NewAccumulator(sk)
151 152 153
	if err != nil {
		return err
	}
154

155
	if err = rs.addUpdate(rs.db, typ, update, true); err != nil {
156 157 158 159 160
		return err
	}
	return nil
}

Sietse Ringers's avatar
Sietse Ringers committed
161 162 163 164
// 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 {
165
		return rs.db.Exists((*EventRecord)(nil), nil)
Sietse Ringers's avatar
Sietse Ringers committed
166 167 168 169 170
	} else {
		return rs.memdb.HasRecords(typ), nil
	}
}

171 172 173
// Revocation update message methods

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

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

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

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

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

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

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

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

	return nil
}
298

299 300 301
// Issuance records

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

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

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

318 319
// Revocation methods

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

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

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

398
	pk, err := rs.Keys.PublicKey(typ.IssuerIdentifier(), sacc.PKCounter)
399
	if err != nil {
400
		return nil, nil, err
401
	}
402
	acc, err := sacc.UnmarshalVerify(pk)
403 404
	if err != nil {
		return nil, nil, err
405
	}
406
	return sacc, acc, 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
func (rs *RevocationStorage) Load(debug bool, dbtype, connstr string, settings map[CredentialTypeIdentifier]*RevocationSetting) error {
469
	var t *CredentialTypeIdentifier
470

471 472
	for typ, s := range settings {
		switch s.Mode {
473 474 475 476
		case RevocationModeServer:
			if s.ServerURL != "" {
				return errors.New("server_url cannot be combined with server mode")
			}
477
			t = &typ
478 479 480
		case RevocationModeProxy:
			t = &typ
		case RevocationModeRequestor: // noop
481
		default:
482 483
			return errors.Errorf(`invalid revocation mode "%s" for %s (supported: "%s", "%s", "%s")`,
				s.Mode, typ, RevocationModeRequestor, RevocationModeServer, RevocationModeProxy)
484
		}
485
	}
486 487 488
	if t != nil && connstr == "" {
		return errors.Errorf("revocation mode for %s requires SQL database but no connection string given", *t)
	}
489 490 491 492 493 494 495

	if connstr == "" {
		Logger.Trace("Using memory revocation database")
		rs.memdb = newMemStorage()
		rs.sqlMode = false
	} else {
		Logger.Trace("Connecting to revocation SQL database")
496
		db, err := newSqlStorage(debug, dbtype, connstr)
497
		if err != nil {
498
			return err
499
		}
500 501
		rs.db = db
		rs.sqlMode = true
502
	}
503 504 505 506 507
	if settings != nil {
		rs.settings = settings
	} else {
		rs.settings = map[CredentialTypeIdentifier]*RevocationSetting{}
	}
508 509 510 511 512 513
	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)
		}
	}
514 515 516
	rs.client = RevocationClient{Conf: rs.conf}
	rs.Keys = RevocationKeys{Conf: rs.conf}
	return nil
517 518
}

519 520 521
func (rs *RevocationStorage) Close() error {
	if rs.db != nil {
		return rs.db.Close()
522 523 524 525
	}
	return nil
}

526
// SetRevocationUpdates retrieves the latest revocation records from the database, and attaches
527 528
// them to the request, for each credential type for which a nonrevocation proof is requested in
// b.Revocation.
529
func (rs *RevocationStorage) SetRevocationUpdates(b *BaseRequest) error {
530 531
	if len(b.Revocation) == 0 {
		return nil
532
	}
533
	var err error
534
	b.RevocationUpdates = make(map[CredentialTypeIdentifier]map[uint]*revocation.Update, len(b.Revocation))
535
	for _, credid := range b.Revocation {
536 537 538
		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
539
		if err = rs.UpdateIfOld(credid); err != nil {
540 541 542 543 544 545 546 547 548 549 550 551
			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
			}
552
		}
553
		b.RevocationUpdates[credid], err = rs.UpdateLatest(credid, RevocationDefaultEventCount)
554 555 556
		if err != nil {
			return err
		}
557
	}
558 559 560 561 562 563
	return nil
}

func (rs *RevocationStorage) getSettings(typ CredentialTypeIdentifier) *RevocationSetting {
	if rs.settings[typ] == nil {
		rs.settings[typ] = &RevocationSetting{}
564
	}
565 566 567 568 569
	s := rs.settings[typ]
	if s.MaxNonrevocationDuration == 0 {
		s.MaxNonrevocationDuration = revocationMaxAccumulatorAge
	}
	return s
570 571
}

572
func (client RevocationClient) PostUpdate(typ CredentialTypeIdentifier, urls []string, update *revocation.Update) {
573
	transport := NewHTTPTransport("")
574
	transport.Binary = true
575
	for _, url := range urls {
576 577
		err := transport.Post(fmt.Sprintf("%s/revocation/update/%s", url, typ.String()), nil, update)
		if err != nil {
578 579
			Logger.Warn("error sending revocation update", err)
		}
580
	}
581 582
}

583 584 585 586 587
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
	}
588
	return NewHTTPTransport(url).Post(
589
		fmt.Sprintf("revocation/issuancerecord/%s/%d", typ, sk.Counter), nil, []byte(message),
590 591 592 593
	)
}

// FetchRevocationRecords gets revocation update messages from the revocation server, of the specified index and greater.
594
func (client RevocationClient) FetchUpdateFrom(typ CredentialTypeIdentifier, pkcounter uint, index uint64) (*revocation.Update, error) {
595 596 597
	update := &revocation.Update{}
	return update, client.getMultiple(
		client.Conf.CredentialTypes[typ].RevocationServers,
598
		fmt.Sprintf("revocation/updatefrom/%s/%d/%d", typ, pkcounter, index),
599 600
		&update,
	)
601
}
602

603 604 605 606 607 608 609
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,
	)
610 611
}

612
func (RevocationClient) getMultiple(urls []string, path string, dest interface{}) error {
613 614 615 616 617
	var (
		errs      multierror.Error
		transport = NewHTTPTransport("")
	)
	transport.Binary = true
618
	for _, url := range urls {
619
		transport.Server = url
620
		err := transport.Get(path, dest)
621
		if err == nil {
622
			return nil
623 624 625
		} else {
			errs.Errors = append(errs.Errors, err)
		}
626
	}
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
	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
643 644
}

645
func (rs RevocationKeys) PrivateKey(issid IssuerIdentifier, counter uint) (*revocation.PrivateKey, error) {
646
	sk, err := rs.Conf.PrivateKey(issid, counter)
647
	if err != nil {
648
		return nil, err
649 650
	}
	if sk == nil {
651
		return nil, errors.Errorf("unknown private key: %s", issid)
652
	}
653
	revsk, err := sk.RevocationKey()
654
	if err != nil {
655
		return nil, err
656
	}
657 658
	return revsk, nil
}
659

660
func (rs RevocationKeys) PublicKey(issid IssuerIdentifier, counter uint) (*revocation.PublicKey, error) {
661
	pk, err := rs.Conf.PublicKey(issid, counter)
662
	if err != nil {
663
		return nil, err
664
	}
665 666
	if pk == nil {
		return nil, errors.Errorf("unknown public key: %s-%d", issid, counter)
667
	}
668 669 670
	revpk, err := pk.RevocationKey()
	if err != nil {
		return nil, err
671
	}
672
	return revpk, nil
673
}
674 675 676 677 678 679 680 681 682 683 684

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

685
func (e *EventRecord) Convert(typ CredentialTypeIdentifier, pkcounter uint, event *revocation.Event) *EventRecord {
686 687 688 689 690
	*e = EventRecord{
		Index:      event.Index,
		E:          (*RevocationAttribute)(event.E),
		ParentHash: eventHash(event.ParentHash),
		CredType:   typ,
691
		PKCounter:  pkcounter,
692 693 694 695 696 697
	}
	return e
}

func (a *AccumulatorRecord) SignedAccumulator() *revocation.SignedAccumulator {
	return &revocation.SignedAccumulator{
698 699
		PKCounter: a.PKCounter,
		Data:      signed.Message(a.Data),
700 701 702 703 704
	}
}

func (a *AccumulatorRecord) Convert(typ CredentialTypeIdentifier, sacc *revocation.SignedAccumulator) *AccumulatorRecord {
	*a = AccumulatorRecord{
705 706 707
		Data:      signedMessage(sacc.Data),
		PKCounter: sacc.PKCounter,
		CredType:  typ,
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 736 737 738 739 740 741 742 743 744 745 746 747 748
	}
	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 ""
	}
}

749 750
func (i *RevocationAttribute) MarshalCBOR() ([]byte, error) {
	return cbor.Marshal((*big.Int)(i), cbor.EncOptions{})
751 752
}

753 754
func (i *RevocationAttribute) UnmarshalCBOR(data []byte) error {
	return cbor.Unmarshal(data, (*big.Int)(i))
755 756 757
}

func (hash eventHash) Value() (driver.Value, error) {
758
	return []byte(hash), nil
759 760 761 762 763 764 765
}

func (hash *eventHash) Scan(src interface{}) error {
	s, ok := src.([]byte)
	if !ok {
		return errors.New("cannot convert source: not a []byte")
	}
766 767
	*hash = make([]byte, len(s))
	copy(*hash, s)
768 769 770 771 772 773 774 775 776 777 778 779 780
	return nil
}

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