revocation.go 13.6 KB
Newer Older
1 2 3 4 5 6 7
package irma

import (
	"fmt"
	"time"

	"github.com/go-errors/errors"
8 9
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/postgres"
10
	"github.com/privacybydesign/gabi/big"
11
	"github.com/privacybydesign/gabi/revocation"
12
	"github.com/privacybydesign/gabi/signed"
13 14
)

15
type (
16 17 18 19 20 21 22 23 24
	RevocationStorage struct {
		conf     *Configuration
		db       revStorage
		memdb    memRevStorage
		sqlMode  bool
		settings map[CredentialTypeIdentifier]*RevocationSetting

		Keys   RevocationKeys
		client RevocationClient
25 26
	}

27 28 29 30 31 32 33 34 35 36 37 38 39 40
	RevocationClient struct {
		Conf *Configuration
	}

	RevocationKeys struct {
		Conf *Configuration
	}

	RevocationSetting struct {
		Mode     RevocationMode `json:"mode"`
		PostURLs []string       `json:"post_urls" mapstructure:"post_urls"`
		updated  time.Time
	}

41
	RevocationMode string
42 43 44

	RevocationRecord struct {
		revocation.Record `gorm:"embedded"`
45
		PublicKeyIndex    uint
46
		CredType          CredentialTypeIdentifier `gorm:"primary_key"`
47 48 49 50 51 52 53 54 55
	}

	TimeRecord struct {
		Index      uint64
		Start, End int64
	}

	// IssuanceRecord contains information generated during issuance, needed for later revocation.
	IssuanceRecord struct {
56 57
		CredType   CredentialTypeIdentifier `gorm:"primary_key"`
		Key        string                   `gorm:"primary_key"`
58 59 60 61 62
		Attr       *big.Int
		Issued     int64
		ValidUntil int64
		RevokedAt  int64 // 0 if not currently revoked
	}
63
)
64

65
const (
66 67 68
	RevocationModeRequestor RevocationMode = ""
	RevocationModeProxy     RevocationMode = "proxy"
	RevocationModeServer    RevocationMode = "server"
69 70
)

71
// Revocation record methods
72

73 74 75 76 77 78 79 80 81 82 83 84 85
func (rs *RevocationStorage) EnableRevocation(typ CredentialTypeIdentifier) error {
	hasRecords, err := rs.db.HasRecords(typ, (*RevocationRecord)(nil))
	if err != nil {
		return err
	}
	if hasRecords {
		return errors.New("revocation record table not empty")
	}

	sk, err := rs.Keys.PrivateKey(typ.IssuerIdentifier())
	if err != nil {
		return err
	}
86 87 88 89
	msg, acc, err := revocation.NewAccumulator(sk)
	if err != nil {
		return err
	}
90 91
	r := &RevocationRecord{
		Record: revocation.Record{
92 93 94
			Message:    msg,
			StartIndex: acc.Index,
			EndIndex:   acc.Index,
95
		},
96 97
		PublicKeyIndex: sk.Counter,
		CredType:       typ,
98 99 100
	}

	if err = rs.AddRevocationRecord(r); err != nil {
101 102 103 104 105 106 107 108
		return err
	}
	return nil
}

// Get returns all records that a client requires to update its revocation state if it is currently
// at the specified index, that is, all records whose end index is greater than or equal to
// the specified index.
109 110 111
func (rs *RevocationStorage) RevocationRecords(typ CredentialTypeIdentifier, index uint64) ([]*RevocationRecord, error) {
	var records []*RevocationRecord
	return records, rs.db.From(typ, "end_index", index, &records)
112 113
}

114 115 116 117 118 119 120 121
func (rs *RevocationStorage) LatestRevocationRecords(typ CredentialTypeIdentifier, count uint64) ([]*RevocationRecord, error) {
	var records []*RevocationRecord
	if rs.sqlMode {
		if err := rs.db.Latest(typ, "end_index", count, &records); err != nil {
			return nil, err
		}
	} else {
		rs.memdb.Latest(typ, count, &records)
122
	}
123 124
	if len(records) == 0 {
		return nil, gorm.ErrRecordNotFound
125
	}
126
	return records, nil
127 128
}

129
func (rs *RevocationStorage) AddRevocationRecords(records []*RevocationRecord) error {
130 131
	var err error
	for _, r := range records {
132
		if err = rs.addRevocationRecord(rs.db, r, false); err != nil {
133 134 135
			return err
		}
	}
136 137 138 139 140 141

	if len(records) > 0 {
		// POST record to listeners, if any, asynchroniously
		go rs.client.PostRevocationRecords(rs.getSettings(records[0].CredType).PostURLs, records)
	}

142 143 144
	return nil
}

145
func (rs *RevocationStorage) AddRevocationRecord(record *RevocationRecord) error {
146
	return rs.addRevocationRecord(rs.db, record, true)
147
}
148

149
func (rs *RevocationStorage) addRevocationRecord(tx revStorage, record *RevocationRecord, post bool) error {
150 151
	// Unmarshal and verify the record against the appropriate public key
	pk, err := rs.Keys.PublicKey(record.CredType.IssuerIdentifier(), record.PublicKeyIndex)
152 153 154
	if err != nil {
		return err
	}
155
	_, err = record.UnmarshalVerify(pk)
156
	if err != nil {
157 158 159
		return err
	}

160 161 162 163
	// Save record
	if rs.sqlMode {
		if err = tx.Insert(record); err != nil {
			return err
164
		}
165 166
	} else {
		rs.memdb.Insert(record)
167 168
	}

169 170
	s := rs.getSettings(record.CredType)
	s.updated = time.Now()
171 172 173 174
	if post {
		// POST record to listeners, if any, asynchroniously
		go rs.client.PostRevocationRecords(s.PostURLs, []*RevocationRecord{record})
	}
175 176 177

	return nil
}
178

179 180 181 182 183 184 185
// 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 {
		return rs.db.HasRecords(typ, (*RevocationRecord)(nil))
	} else {
		return rs.memdb.HasRecords(typ), nil
186
	}
187
}
188

189 190 191 192
// Issuance records

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

195 196
func (rs *RevocationStorage) AddIssuanceRecord(r *IssuanceRecord) error {
	return rs.db.Insert(r)
197 198
}

199 200 201 202 203
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
204
	}
205 206
	return &r, nil
}
207

208 209 210 211 212 213 214
// Revocation methods

// Revoke revokes the credential specified specified by key if found within the current database,
// by updating its revocation time to now, adding its revocation attribute to the current accumulator,
// and updating the revocation database on disk.
func (rs *RevocationStorage) Revoke(typ CredentialTypeIdentifier, key string) error {
	sk, err := rs.conf.PrivateKey(typ.IssuerIdentifier())
215 216 217
	if err != nil {
		return err
	}
218 219 220 221 222
	if sk == nil {
		return errors.New("private key not found")
	}
	rsk, err := sk.RevocationKey()
	if err != nil {
223 224 225
		return err
	}

226 227 228 229 230 231 232 233 234 235 236
	return rs.db.Transaction(func(tx revStorage) error {
		var err error
		cr := IssuanceRecord{}
		if err = tx.Get(typ, "key", key, &cr); err != nil {
			return err
		}
		cr.RevokedAt = time.Now().UnixNano()
		if err = tx.Save(&cr); err != nil {
			return err
		}
		return rs.revokeAttr(tx, typ, rsk, cr.Attr)
237 238 239
	})
}

240 241 242 243 244 245 246 247 248 249
func (rs *RevocationStorage) revokeAttr(tx revStorage, typ CredentialTypeIdentifier, sk *revocation.PrivateKey, e *big.Int) error {
	cur, err := rs.currentAccumulator(tx, typ)
	if err != nil {
		return err
	}
	if cur == nil {
		return errors.Errorf("cannot revoke for type %s, not enabled yet", typ)
	}

	newAcc, err := cur.Remove(sk, e)
250 251 252
	if err != nil {
		return err
	}
253
	update := &revocation.AccumulatorUpdate{
254 255 256 257 258 259 260 261 262
		Accumulator: *newAcc,
		StartIndex:  newAcc.Index,
		Revoked:     []*big.Int{e},
		Time:        time.Now().UnixNano(),
	}
	updateMsg, err := signed.MarshalSign(sk.ECDSA, update)
	if err != nil {
		return err
	}
263 264
	record := &RevocationRecord{
		Record: revocation.Record{
265 266 267
			StartIndex: newAcc.Index,
			EndIndex:   newAcc.Index,
			Message:    updateMsg,
268
		},
269 270
		PublicKeyIndex: sk.Counter,
		CredType:       typ,
271
	}
272
	if err = rs.addRevocationRecord(tx, record, true); err != nil {
273 274 275 276 277
		return err
	}
	return nil
}

278
// Accumulator methods
279

280 281
func (rs *RevocationStorage) CurrentAccumulator(typ CredentialTypeIdentifier) (*revocation.Accumulator, error) {
	return rs.currentAccumulator(rs.db, typ)
282 283
}

284 285
func (rs *RevocationStorage) currentAccumulator(tx revStorage, typ CredentialTypeIdentifier) (rec *revocation.Accumulator, err error) {
	record := &RevocationRecord{}
286

287 288 289 290 291
	if rs.sqlMode {
		if err := tx.Last(typ, record); err != nil {
			if gorm.IsRecordNotFoundError(err) {
				return nil, nil
			}
292 293
			return nil, err
		}
294 295 296 297 298 299 300
	} else {
		var r []*RevocationRecord
		rs.memdb.Latest(typ, 1, &r)
		if len(r) == 0 {
			return nil, nil
		}
		record = r[0]
301 302
	}

303
	pk, err := rs.Keys.PublicKey(typ.IssuerIdentifier(), record.PublicKeyIndex)
304 305 306
	if err != nil {
		return nil, err
	}
307 308
	var u revocation.AccumulatorUpdate
	if err = signed.UnmarshalVerify(pk.ECDSA, record.Message, &u); err != nil {
309 310
		return nil, err
	}
311
	return &u.Accumulator, nil
312 313
}

314 315 316 317
// Methods to update from remote revocation server

func (rs *RevocationStorage) UpdateDB(typ CredentialTypeIdentifier) error {
	records, err := rs.client.FetchLatestRevocationRecords(typ, revocationUpdateCount)
318
	if err != nil {
319
		return err
320
	}
321
	return rs.AddRevocationRecords(records)
322 323
}

324 325 326
func (rs *RevocationStorage) updateIfOld(typ CredentialTypeIdentifier) error {
	if rs.getSettings(typ).updated.Before(time.Now().Add(-5 * time.Minute)) {
		if err := rs.UpdateDB(typ); err != nil {
327 328 329 330 331 332
			return err
		}
	}
	return nil
}

333 334 335 336 337 338 339 340
// 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.
func (rs *RevocationStorage) SaveIssuanceRecord(typ CredentialTypeIdentifier, rec *IssuanceRecord) error {
	// TODO store locally if appropriate?

	// Just store it if we are the revocation server for this credential type
	if rs.getSettings(typ).Mode == RevocationModeServer {
		return rs.AddIssuanceRecord(rec)
341 342
	}

343 344 345 346 347 348 349 350 351
	// We have to send it, sign it first
	credtype := rs.conf.CredentialTypes[typ]
	if credtype == nil {
		return errors.New("unknown credential type")
	}
	if credtype.RevocationServer == "" {
		return errors.New("credential type has no revocation server")
	}
	sk, err := rs.Keys.PrivateKey(typ.IssuerIdentifier())
352 353 354
	if err != nil {
		return err
	}
355
	message, err := signed.MarshalSign(sk.ECDSA, rec)
356 357 358
	if err != nil {
		return err
	}
359 360

	return rs.client.PostIssuanceRecord(typ, sk.Counter, message)
361 362
}

363 364 365
// Misscelaneous methods

func (rs *RevocationStorage) Load(debug bool, connstr string, settings map[CredentialTypeIdentifier]*RevocationSetting) error {
366 367 368 369 370 371 372 373
	var t *CredentialTypeIdentifier
	for typ, s := range settings {
		switch s.Mode {
		case RevocationModeServer, RevocationModeProxy:
			t = &typ
		default:
			return errors.Errorf("invalid revocation mode '%s' for %s (supported: %s, %s)",
				s.Mode, typ, RevocationModeServer, RevocationModeProxy)
374
		}
375
	}
376 377 378
	if t != nil && connstr == "" {
		return errors.Errorf("revocation mode for %s requires SQL database but no connection string given", *t)
	}
379 380 381 382 383 384 385 386

	if connstr == "" {
		Logger.Trace("Using memory revocation database")
		rs.memdb = newMemStorage()
		rs.sqlMode = false
	} else {
		Logger.Trace("Connecting to revocation SQL database")
		db, err := newSqlStorage(debug, connstr)
387
		if err != nil {
388
			return err
389
		}
390 391
		rs.db = db
		rs.sqlMode = true
392
	}
393 394 395 396 397 398 399 400
	if settings != nil {
		rs.settings = settings
	} else {
		rs.settings = map[CredentialTypeIdentifier]*RevocationSetting{}
	}
	rs.client = RevocationClient{Conf: rs.conf}
	rs.Keys = RevocationKeys{Conf: rs.conf}
	return nil
401 402
}

403 404 405
func (rs *RevocationStorage) Close() error {
	if rs.db != nil {
		return rs.db.Close()
406 407 408 409
	}
	return nil
}

410 411 412 413 414 415
// SetRevocationRecords retrieves the latest revocation records from the database, and attaches
// them to the request, for each credential type for which a nonrevocation proof is requested in
// b.Revocation.
func (rs *RevocationStorage) SetRevocationRecords(b *BaseRequest) error {
	if len(b.Revocation) == 0 {
		return nil
416
	}
417 418 419 420 421 422 423 424 425 426
	var err error
	b.RevocationUpdates = make(map[CredentialTypeIdentifier][]*RevocationRecord, len(b.Revocation))
	for _, credid := range b.Revocation {
		if err = rs.updateIfOld(credid); err != nil {
			return err
		}
		b.RevocationUpdates[credid], err = rs.LatestRevocationRecords(credid, revocationUpdateCount)
		if err != nil {
			return err
		}
427
	}
428 429 430 431 432 433
	return nil
}

func (rs *RevocationStorage) getSettings(typ CredentialTypeIdentifier) *RevocationSetting {
	if rs.settings[typ] == nil {
		rs.settings[typ] = &RevocationSetting{}
434
	}
435 436 437
	return rs.settings[typ]
}

438
func (RevocationClient) PostRevocationRecords(urls []string, records []*RevocationRecord) {
439
	transport := NewHTTPTransport("")
440 441
	for _, url := range urls {
		if err := transport.Post(url+"/-/revocation/records", nil, &records); err != nil {
442 443
			Logger.Warn("error sending revocation update", err)
		}
444
	}
445 446 447 448 449 450 451 452 453 454 455 456 457
}

func (client RevocationClient) PostIssuanceRecord(typ CredentialTypeIdentifier, counter uint, message signed.Message) error {
	return NewHTTPTransport(client.Conf.CredentialTypes[typ].RevocationServer).Post(
		fmt.Sprintf("-/revocation/issuancerecord/%s/%d", typ, counter), nil, []byte(message),
	)
}

// FetchRevocationRecords gets revocation update messages from the revocation server, of the specified index and greater.
func (client RevocationClient) FetchRevocationRecords(typ CredentialTypeIdentifier, index uint64) ([]*RevocationRecord, error) {
	var records []*RevocationRecord
	err := NewHTTPTransport(client.Conf.CredentialTypes[typ].RevocationServer).
		Get(fmt.Sprintf("-/revocation/records/%s/%d", typ, index), &records)
458
	if err != nil {
459
		return nil, err
460
	}
461 462
	return records, nil
}
463

464 465 466 467
func (client RevocationClient) FetchLatestRevocationRecords(typ CredentialTypeIdentifier, count uint64) ([]*RevocationRecord, error) {
	var records []*RevocationRecord
	err := NewHTTPTransport(client.Conf.CredentialTypes[typ].RevocationServer).
		Get(fmt.Sprintf("-/revocation/latestrecords/%s/%d", typ, count), &records)
468
	if err != nil {
469
		return nil, err
470
	}
471
	return records, nil
472 473
}

474 475
func (rs RevocationKeys) PrivateKey(issid IssuerIdentifier) (*revocation.PrivateKey, error) {
	sk, err := rs.Conf.PrivateKey(issid)
476
	if err != nil {
477
		return nil, err
478 479
	}
	if sk == nil {
480
		return nil, errors.Errorf("unknown private key: %s", issid)
481
	}
482
	revsk, err := sk.RevocationKey()
483
	if err != nil {
484
		return nil, err
485
	}
486 487
	return revsk, nil
}
488

489 490
func (rs RevocationKeys) PublicKey(issid IssuerIdentifier, counter uint) (*revocation.PublicKey, error) {
	pk, err := rs.Conf.PublicKey(issid, int(counter))
491
	if err != nil {
492
		return nil, err
493
	}
494 495
	if pk == nil {
		return nil, errors.Errorf("unknown public key: %s-%d", issid, counter)
496
	}
497 498 499
	revpk, err := pk.RevocationKey()
	if err != nil {
		return nil, err
500
	}
501
	return revpk, nil
502
}