revocation.go 21.6 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 77
		CredType CredentialTypeIdentifier `gorm:"primary_key"`
		Data     signedMessage
		PKIndex  uint
78 79
	}

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

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

	// TODO
	TimeRecord struct {
		Index      uint64
		Start, End int64
	}
102
)
103

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

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

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
135 136 137
// 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.
138
func (rs *RevocationStorage) EnableRevocation(typ CredentialTypeIdentifier, sk *revocation.PrivateKey) error {
139
	hasRecords, err := rs.db.HasRecords(typ, (*EventRecord)(nil))
140 141 142 143
	if err != nil {
		return err
	}
	if hasRecords {
144
		return errors.New("revocation event record table not empty")
145 146
	}

147
	update, err := revocation.NewAccumulator(sk)
148 149 150
	if err != nil {
		return err
	}
151

152
	if err = rs.addUpdate(rs.db, typ, update, true); err != nil {
153 154 155 156 157
		return err
	}
	return nil
}

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

168 169 170
// Revocation update message methods

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

193 194
func (rs *RevocationStorage) UpdateLatest(typ CredentialTypeIdentifier, count uint64) (*revocation.Update, error) {
	// TODO what should this function and UpdateFrom return when no records are found?
195
	if rs.sqlMode {
196 197 198 199 200 201 202
		var update *revocation.Update
		if err := rs.db.Transaction(func(tx revStorage) error {
			acc, _, err := rs.currentAccumulator(tx, typ)
			if err != nil {
				return err
			}
			var events []*EventRecord
203
			if err := tx.Latest(typ, "eventindex", count, &events); err != nil {
204 205 206 207 208
				return err
			}
			update = rs.newUpdate(acc, events)
			return nil
		}); err != nil {
209 210
			return nil, err
		}
211
		return update, nil
212
	} else {
213
		return rs.memdb.Latest(typ, count), nil
214 215 216
	}
}

217 218 219
func (*RevocationStorage) newUpdate(acc *revocation.SignedAccumulator, events []*EventRecord) *revocation.Update {
	updates := make([]*revocation.Event, len(events))
	for i := range events {
220
		updates[i] = events[i].Event()
221
	}
222 223 224
	return &revocation.Update{
		SignedAccumulator: acc,
		Events:            updates,
225
	}
226 227
}

228
func (rs *RevocationStorage) AddUpdate(typ CredentialTypeIdentifier, record *revocation.Update) error {
229
	return rs.addUpdate(rs.db, typ, record, false)
230
}
231

232
func (rs *RevocationStorage) addUpdate(tx revStorage, typ CredentialTypeIdentifier, update *revocation.Update, create bool) error {
233
	// Unmarshal and verify the record against the appropriate public key
234
	pk, err := rs.Keys.PublicKey(typ.IssuerIdentifier(), update.SignedAccumulator.PKIndex)
235 236 237
	if err != nil {
		return err
	}
238
	if _, _, err = update.Verify(pk, 0); err != nil {
239 240 241
		return err
	}

242 243
	// Save record
	if rs.sqlMode {
244 245 246 247
		save := tx.Save
		if create {
			save = tx.Insert
		}
248
		if err = save(new(AccumulatorRecord).Convert(typ, update.SignedAccumulator)); err != nil {
249
			return err
250
		}
251
		for _, event := range update.Events {
252
			if err = tx.Insert(new(EventRecord).Convert(typ, event)); err != nil {
253 254 255
				return err
			}
		}
256
	} else {
257
		rs.memdb.Insert(typ, update)
258 259
	}

260
	s := rs.getSettings(typ)
261
	s.updated = time.Now()
262 263
	// POST record to listeners, if any, asynchroniously
	go rs.client.PostUpdate(typ, s.PostURLs, update)
264 265 266

	return nil
}
267

268 269 270 271
// Issuance records

func (rs *RevocationStorage) IssuanceRecordExists(typ CredentialTypeIdentifier, key []byte) (bool, error) {
	return rs.db.Exists(typ, "key", key, &IssuanceRecord{})
272 273
}

274 275
func (rs *RevocationStorage) AddIssuanceRecord(r *IssuanceRecord) error {
	return rs.db.Insert(r)
276 277
}

278 279 280 281 282
func (rs *RevocationStorage) IssuanceRecord(typ CredentialTypeIdentifier, key []byte) (*IssuanceRecord, error) {
	var r IssuanceRecord
	err := rs.db.Get(typ, "key", key, &r)
	if err != nil {
		return nil, err
283
	}
284 285
	return &r, nil
}
286

287 288
// Revocation methods

Sietse Ringers's avatar
Sietse Ringers committed
289 290
// 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,
291
// and updating the revocation database on disk.
292
func (rs *RevocationStorage) Revoke(typ CredentialTypeIdentifier, key string, sk *revocation.PrivateKey) error {
Sietse Ringers's avatar
Sietse Ringers committed
293 294
	if rs.getSettings(typ).Mode != RevocationModeServer {
		return errors.Errorf("cannot revoke %s", typ)
295
	}
296

297 298
	return rs.db.Transaction(func(tx revStorage) error {
		var err error
299
		issrecord := IssuanceRecord{}
300
		if err = tx.Get(typ, "revocationkey", key, &issrecord); err != nil {
301 302
			return err
		}
303 304
		issrecord.RevokedAt = time.Now().UnixNano()
		if err = tx.Save(&issrecord); err != nil {
305 306
			return err
		}
307
		return rs.revokeAttr(tx, typ, sk, issrecord.Attr)
308 309 310
	})
}

311
func (rs *RevocationStorage) revokeAttr(tx revStorage, typ CredentialTypeIdentifier, sk *revocation.PrivateKey, e *RevocationAttribute) error {
312
	_, cur, err := rs.currentAccumulator(tx, typ)
313 314 315 316 317 318
	if err != nil {
		return err
	}
	if cur == nil {
		return errors.Errorf("cannot revoke for type %s, not enabled yet", typ)
	}
319 320
	var parent EventRecord
	if err = rs.db.Last(typ, &parent); err != nil {
321 322
		return err
	}
323

324
	update, err := cur.Remove(sk, (*big.Int)(e), parent.Event())
325 326 327
	if err != nil {
		return err
	}
328
	if err = rs.addUpdate(tx, typ, update, false); err != nil {
329 330 331 332 333
		return err
	}
	return nil
}

334
// Accumulator methods
335

336 337 338 339
func (rs *RevocationStorage) currentAccumulator(tx revStorage, typ CredentialTypeIdentifier) (
	*revocation.SignedAccumulator, *revocation.Accumulator, error,
) {
	var err error
340
	var sacc *revocation.SignedAccumulator
341
	if rs.sqlMode {
342
		record := &AccumulatorRecord{}
343
		if err = tx.Last(typ, record); err != nil {
344
			if gorm.IsRecordNotFoundError(err) {
345
				return nil, nil, nil
346
			}
347
		}
348
		sacc = record.SignedAccumulator()
349
	} else {
350 351 352
		u := rs.memdb.Latest(typ, 0)
		if u == nil {
			return nil, nil, nil
353
		}
354
		sacc = u.SignedAccumulator
355 356
	}

357
	pk, err := rs.Keys.PublicKey(typ.IssuerIdentifier(), sacc.PKIndex)
358
	if err != nil {
359
		return nil, nil, err
360
	}
361
	acc, err := sacc.UnmarshalVerify(pk)
362 363
	if err != nil {
		return nil, nil, err
364
	}
365
	return sacc, acc, nil
366 367
}

368 369 370
// Methods to update from remote revocation server

func (rs *RevocationStorage) UpdateDB(typ CredentialTypeIdentifier) error {
371
	update, err := rs.client.FetchUpdateLatest(typ, revocationUpdateCount)
372
	if err != nil {
373
		return err
374
	}
375

376
	if err = rs.AddUpdate(typ, update); err != nil {
377 378 379 380 381 382
		return err
	}

	// bump updated even if no new records were added
	rs.getSettings(typ).updated = time.Now()
	return nil
383 384
}

Sietse Ringers's avatar
Sietse Ringers committed
385
func (rs *RevocationStorage) UpdateIfOld(typ CredentialTypeIdentifier) error {
386
	settings := rs.getSettings(typ)
387
	// update 10 seconds before the maximum, to stay below it
388
	if settings.updated.Before(time.Now().Add(time.Duration(-settings.MaxNonrevocationDuration+10) * time.Second)) {
389
		if err := rs.UpdateDB(typ); err != nil {
390 391 392 393 394 395
			return err
		}
	}
	return nil
}

396 397
// 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.
398 399 400 401 402 403 404 405 406
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")
	}

407
	// Just store it if we are the revocation server for this credential type
408 409
	settings := rs.getSettings(typ)
	if settings.Mode == RevocationModeServer {
410
		return rs.AddIssuanceRecord(rec)
411 412
	}

413
	// We have to send it, sign it first
414 415 416
	if settings.ServerURL == "" {
		return errors.New("cannot send issuance record: no server_url configured")
	}
417
	rsk, err := sk.RevocationKey()
418 419 420
	if err != nil {
		return err
	}
421
	return rs.client.PostIssuanceRecord(typ, rsk, rec, settings.ServerURL)
422 423
}

424 425
// Misscelaneous methods

426
func (rs *RevocationStorage) Load(debug bool, dbtype, connstr string, settings map[CredentialTypeIdentifier]*RevocationSetting) error {
427
	var t *CredentialTypeIdentifier
428

429 430
	for typ, s := range settings {
		switch s.Mode {
431 432 433 434
		case RevocationModeServer:
			if s.ServerURL != "" {
				return errors.New("server_url cannot be combined with server mode")
			}
435
			t = &typ
436 437 438
		case RevocationModeProxy:
			t = &typ
		case RevocationModeRequestor: // noop
439
		default:
440 441
			return errors.Errorf(`invalid revocation mode "%s" for %s (supported: "%s", "%s", "%s")`,
				s.Mode, typ, RevocationModeRequestor, RevocationModeServer, RevocationModeProxy)
442
		}
443
	}
444 445 446
	if t != nil && connstr == "" {
		return errors.Errorf("revocation mode for %s requires SQL database but no connection string given", *t)
	}
447 448 449 450 451 452 453

	if connstr == "" {
		Logger.Trace("Using memory revocation database")
		rs.memdb = newMemStorage()
		rs.sqlMode = false
	} else {
		Logger.Trace("Connecting to revocation SQL database")
454
		db, err := newSqlStorage(debug, dbtype, connstr)
455
		if err != nil {
456
			return err
457
		}
458 459
		rs.db = db
		rs.sqlMode = true
460
	}
461 462 463 464 465
	if settings != nil {
		rs.settings = settings
	} else {
		rs.settings = map[CredentialTypeIdentifier]*RevocationSetting{}
	}
466 467 468 469 470 471
	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)
		}
	}
472 473 474
	rs.client = RevocationClient{Conf: rs.conf}
	rs.Keys = RevocationKeys{Conf: rs.conf}
	return nil
475 476
}

477 478 479
func (rs *RevocationStorage) Close() error {
	if rs.db != nil {
		return rs.db.Close()
480 481 482 483
	}
	return nil
}

484
// SetRevocationUpdates retrieves the latest revocation records from the database, and attaches
485 486
// them to the request, for each credential type for which a nonrevocation proof is requested in
// b.Revocation.
487
func (rs *RevocationStorage) SetRevocationUpdates(b *BaseRequest) error {
488 489
	if len(b.Revocation) == 0 {
		return nil
490
	}
491
	var err error
492
	b.RevocationUpdates = make(map[CredentialTypeIdentifier]*revocation.Update, len(b.Revocation))
493
	for _, credid := range b.Revocation {
494 495 496
		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
497
		if err = rs.UpdateIfOld(credid); err != nil {
498 499 500 501 502 503 504 505 506 507 508 509
			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
			}
510
		}
511
		b.RevocationUpdates[credid], err = rs.UpdateLatest(credid, revocationUpdateCount)
512 513 514
		if err != nil {
			return err
		}
515
	}
516 517 518 519 520 521
	return nil
}

func (rs *RevocationStorage) getSettings(typ CredentialTypeIdentifier) *RevocationSetting {
	if rs.settings[typ] == nil {
		rs.settings[typ] = &RevocationSetting{}
522
	}
523 524 525 526 527
	s := rs.settings[typ]
	if s.MaxNonrevocationDuration == 0 {
		s.MaxNonrevocationDuration = revocationMaxAccumulatorAge
	}
	return s
528 529
}

530
func (client RevocationClient) PostUpdate(typ CredentialTypeIdentifier, urls []string, update *revocation.Update) {
531
	transport := NewHTTPTransport("")
532
	transport.Binary = true
533
	for _, url := range urls {
534 535
		err := transport.Post(fmt.Sprintf("%s/revocation/update/%s", url, typ.String()), nil, update)
		if err != nil {
536 537
			Logger.Warn("error sending revocation update", err)
		}
538
	}
539 540
}

541 542 543 544 545
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
	}
546
	return NewHTTPTransport(url).Post(
547
		fmt.Sprintf("revocation/issuancerecord/%s/%d", typ, sk.Counter), nil, []byte(message),
548 549 550 551
	)
}

// FetchRevocationRecords gets revocation update messages from the revocation server, of the specified index and greater.
552 553
func (client RevocationClient) FetchUpdateFrom(typ CredentialTypeIdentifier, index uint64) (*revocation.Update, error) {
	return client.fetchUpdate(typ, "updatefrom", index)
554
}
555

556 557 558 559 560
func (client RevocationClient) FetchUpdateLatest(typ CredentialTypeIdentifier, count uint64) (*revocation.Update, error) {
	return client.fetchUpdate(typ, "updatelatest", count)
}

func (client RevocationClient) fetchUpdate(typ CredentialTypeIdentifier, u string, i uint64) (*revocation.Update, error) {
561 562 563 564 565 566 567
	var (
		err       error
		errs      multierror.Error
		update    = &revocation.Update{}
		transport = NewHTTPTransport("")
	)
	transport.Binary = true
568 569
	for _, url := range client.Conf.CredentialTypes[typ].RevocationServers {
		transport.Server = url
570
		err = transport.Get(fmt.Sprintf("revocation/%s/%s/%d", u, typ, i), &update)
571
		if err == nil {
572
			return update, nil
573 574 575
		} else {
			errs.Errors = append(errs.Errors, err)
		}
576
	}
577
	return nil, errors.WrapPrefix(errs, "failed to download revocation update", 0)
578 579
}

580 581
func (rs RevocationKeys) PrivateKey(issid IssuerIdentifier) (*revocation.PrivateKey, error) {
	sk, err := rs.Conf.PrivateKey(issid)
582
	if err != nil {
583
		return nil, err
584 585
	}
	if sk == nil {
586
		return nil, errors.Errorf("unknown private key: %s", issid)
587
	}
588
	revsk, err := sk.RevocationKey()
589
	if err != nil {
590
		return nil, err
591
	}
592 593
	return revsk, nil
}
594

595 596
func (rs RevocationKeys) PublicKey(issid IssuerIdentifier, counter uint) (*revocation.PublicKey, error) {
	pk, err := rs.Conf.PublicKey(issid, int(counter))
597
	if err != nil {
598
		return nil, err
599
	}
600 601
	if pk == nil {
		return nil, errors.Errorf("unknown public key: %s-%d", issid, counter)
602
	}
603 604 605
	revpk, err := pk.RevocationKey()
	if err != nil {
		return nil, err
606
	}
607
	return revpk, nil
608
}
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682

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

func (e *EventRecord) Convert(typ CredentialTypeIdentifier, event *revocation.Event) *EventRecord {
	*e = EventRecord{
		Index:      event.Index,
		E:          (*RevocationAttribute)(event.E),
		ParentHash: eventHash(event.ParentHash),
		CredType:   typ,
	}
	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 ""
	}
}

683 684
func (i *RevocationAttribute) MarshalCBOR() ([]byte, error) {
	return cbor.Marshal((*big.Int)(i), cbor.EncOptions{})
685 686
}

687 688
func (i *RevocationAttribute) UnmarshalCBOR(data []byte) error {
	return cbor.Unmarshal(data, (*big.Int)(i))
689 690 691
}

func (hash eventHash) Value() (driver.Value, error) {
692
	return []byte(hash), nil
693 694 695 696 697 698 699
}

func (hash *eventHash) Scan(src interface{}) error {
	s, ok := src.([]byte)
	if !ok {
		return errors.New("cannot convert source: not a []byte")
	}
700 701
	*hash = make([]byte, len(s))
	copy(*hash, s)
702 703 704 705 706 707 708 709 710 711 712 713 714
	return nil
}

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