revocation.go 23.8 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
		if err := rs.UpdateDB(typ); err != nil {
431 432 433 434 435 436
			return err
		}
	}
	return nil
}

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

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

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

465 466
// Misscelaneous methods

467
func (rs *RevocationStorage) Load(debug bool, dbtype, connstr string, settings map[CredentialTypeIdentifier]*RevocationSetting) error {
468
	var t *CredentialTypeIdentifier
469

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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