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

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

403 404 405
// Methods to update from remote revocation server

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

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

431 432
// 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.
433 434 435 436 437 438 439 440 441
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")
	}

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

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

459 460
// Misscelaneous methods

461
func (rs *RevocationStorage) Load(debug bool, dbtype, connstr string, settings map[CredentialTypeIdentifier]*RevocationSetting) error {
462
	var t *CredentialTypeIdentifier
463

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

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

512 513 514
func (rs *RevocationStorage) Close() error {
	if rs.db != nil {
		return rs.db.Close()
515 516 517 518
	}
	return nil
}

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

func (rs *RevocationStorage) getSettings(typ CredentialTypeIdentifier) *RevocationSetting {
	if rs.settings[typ] == nil {
		rs.settings[typ] = &RevocationSetting{}
557
	}
558 559 560 561 562
	s := rs.settings[typ]
	if s.MaxNonrevocationDuration == 0 {
		s.MaxNonrevocationDuration = revocationMaxAccumulatorAge
	}
	return s
563 564
}

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

576 577 578 579 580
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
	}
581
	return NewHTTPTransport(url).Post(
582
		fmt.Sprintf("revocation/issuancerecord/%s/%d", typ, sk.Counter), nil, []byte(message),
583 584 585 586
	)
}

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

596 597 598 599 600 601 602
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,
	)
603 604
}

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

638
func (rs RevocationKeys) PrivateKey(issid IssuerIdentifier, counter uint) (*revocation.PrivateKey, error) {
639
	sk, err := rs.Conf.PrivateKey(issid, counter)
640
	if err != nil {
641
		return nil, err
642 643
	}
	if sk == nil {
644
		return nil, errors.Errorf("unknown private key: %s", issid)
645
	}
646
	revsk, err := sk.RevocationKey()
647
	if err != nil {
648
		return nil, err
649
	}
650 651
	return revsk, nil
}
652

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

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

678
func (e *EventRecord) Convert(typ CredentialTypeIdentifier, pkcounter uint, event *revocation.Event) *EventRecord {
679 680 681 682 683
	*e = EventRecord{
		Index:      event.Index,
		E:          (*RevocationAttribute)(event.E),
		ParentHash: eventHash(event.ParentHash),
		CredType:   typ,
684
		PKCounter:  pkcounter,
685 686 687 688 689 690
	}
	return e
}

func (a *AccumulatorRecord) SignedAccumulator() *revocation.SignedAccumulator {
	return &revocation.SignedAccumulator{
691 692
		PKCounter: a.PKCounter,
		Data:      signed.Message(a.Data),
693 694 695 696 697
	}
}

func (a *AccumulatorRecord) Convert(typ CredentialTypeIdentifier, sacc *revocation.SignedAccumulator) *AccumulatorRecord {
	*a = AccumulatorRecord{
698 699 700
		Data:      signedMessage(sacc.Data),
		PKCounter: sacc.PKCounter,
		CredType:  typ,
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 736 737 738 739 740 741
	}
	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 ""
	}
}

742 743
func (i *RevocationAttribute) MarshalCBOR() ([]byte, error) {
	return cbor.Marshal((*big.Int)(i), cbor.EncOptions{})
744 745
}

746 747
func (i *RevocationAttribute) UnmarshalCBOR(data []byte) error {
	return cbor.Unmarshal(data, (*big.Int)(i))
748 749 750
}

func (hash eventHash) Value() (driver.Value, error) {
751
	return []byte(hash), nil
752 753 754 755 756 757 758
}

func (hash *eventHash) Scan(src interface{}) error {
	s, ok := src.([]byte)
	if !ok {
		return errors.New("cannot convert source: not a []byte")
	}
759 760
	*hash = make([]byte, len(s))
	copy(*hash, s)
761 762 763 764 765 766 767 768 769 770 771 772 773
	return nil
}

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