irmaconfig.go 49.3 KB
Newer Older
1
package irma
2
3

import (
4
	"crypto/rsa"
5
6
7
8
9
	"encoding/base64"
	"encoding/xml"
	"io/ioutil"
	"os"
	"path/filepath"
10
	"reflect"
11
	"regexp"
12
	"runtime"
13
	"strconv"
14
	"time"
15

16
17
	"crypto/sha256"

18
19
20
21
	"fmt"

	"strings"

22
23
24
25
26
27
28
29
30
	"sort"

	"bytes"

	"encoding/hex"

	"crypto/x509"
	"encoding/pem"

31
	"github.com/dgrijalva/jwt-go"
32
	"github.com/go-errors/errors"
Sietse Ringers's avatar
Sietse Ringers committed
33
	"github.com/jasonlvhit/gocron"
34
35
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/big"
36
	"github.com/privacybydesign/gabi/signed"
37
	"github.com/privacybydesign/irmago/internal/fs"
38
39
)

40
// Configuration keeps track of scheme managers, issuers, credential types and public keys,
41
// dezerializing them from an irma_configuration folder, and downloads and saves new ones on demand.
42
type Configuration struct {
43
44
45
	SchemeManagers  map[SchemeManagerIdentifier]*SchemeManager
	Issuers         map[IssuerIdentifier]*Issuer
	CredentialTypes map[CredentialTypeIdentifier]*CredentialType
46
	AttributeTypes  map[AttributeTypeIdentifier]*AttributeType
47

48
49
	RevocationStorage *RevocationStorage

Sietse Ringers's avatar
Sietse Ringers committed
50
	// Path to the irma_configuration folder that this instance represents
51
52
	Path           string
	RevocationPath string
Sietse Ringers's avatar
Sietse Ringers committed
53

54
55
56
	// DisabledSchemeManagers keeps track of scheme managers that did not parse  succesfully
	// (i.e., invalid signature, parsing error), and the problem that occurred when parsing them
	DisabledSchemeManagers map[SchemeManagerIdentifier]*SchemeManagerError
57

58
59
	Warnings []string

60
	kssPublicKeys map[SchemeManagerIdentifier]map[int]*rsa.PublicKey
61
	publicKeys    map[IssuerIdentifier]map[int]*gabi.PublicKey
62
	privateKeys   map[IssuerIdentifier]*gabi.PrivateKey
63
	reverseHashes map[string]CredentialTypeIdentifier
64
	initialized   bool
65
	assets        string
66
	readOnly      bool
Sietse Ringers's avatar
Sietse Ringers committed
67
	cronchan      chan bool
68
	scheduler     *gocron.Scheduler
69
70
}

Sietse Ringers's avatar
Sietse Ringers committed
71
72
// ConfigurationFileHash encodes the SHA256 hash of an authenticated
// file under a scheme manager within the configuration folder.
73
74
type ConfigurationFileHash []byte

Sietse Ringers's avatar
Sietse Ringers committed
75
76
// SchemeManagerIndex is a (signed) list of files under a scheme manager
// along with their SHA266 hash
77
78
type SchemeManagerIndex map[string]ConfigurationFileHash

79
80
type SchemeManagerStatus string

81
82
type SchemeManagerError struct {
	Manager SchemeManagerIdentifier
83
	Status  SchemeManagerStatus
84
85
86
	Err     error
}

87
88
89
90
91
type UnknownIdentifierError struct {
	ErrorType
	Missing *IrmaIdentifierSet
}

Leon's avatar
Leon committed
92
93
94
95
96
type RequiredAttributeMissingError struct {
	ErrorType
	Missing *IrmaIdentifierSet
}

97
98
99
100
101
102
103
const (
	SchemeManagerStatusValid               = SchemeManagerStatus("Valid")
	SchemeManagerStatusUnprocessed         = SchemeManagerStatus("Unprocessed")
	SchemeManagerStatusInvalidIndex        = SchemeManagerStatus("InvalidIndex")
	SchemeManagerStatusInvalidSignature    = SchemeManagerStatus("InvalidSignature")
	SchemeManagerStatusParsingError        = SchemeManagerStatus("ParsingError")
	SchemeManagerStatusContentParsingError = SchemeManagerStatus("ContentParsingError")
104

105
106
	pubkeyPattern  = "%s/%s/%s/PublicKeys/*.xml"
	privkeyPattern = "%s/%s/%s/PrivateKeys/*.xml"
107
108
)

109
110
111
112
var (
	validLangs = []string{"en", "nl"} // Hardcode these for now, TODO make configurable
)

113
114
115
116
func (sme SchemeManagerError) Error() string {
	return fmt.Sprintf("Error parsing scheme manager %s: %s", sme.Manager.Name(), sme.Err.Error())
}

117
// NewConfiguration returns a new configuration. After this
118
// ParseFolder() should be called to parse the specified path.
119
120
121
122
func NewConfiguration(path string) (*Configuration, error) {
	return newConfiguration(path, "")
}

123
124
// NewConfigurationReadOnly returns a new configuration whose representation on disk
// is never altered. ParseFolder() should be called to parse the specified path.
125
126
127
128
129
130
131
132
133
func NewConfigurationReadOnly(path string) (*Configuration, error) {
	conf, err := newConfiguration(path, "")
	if err != nil {
		return nil, err
	}
	conf.readOnly = true
	return conf, nil
}

134
135
// NewConfigurationFromAssets returns a new configuration, copying the schemes out of the assets folder to path.
// ParseFolder() should be called to parse the specified path.
136
137
138
139
140
func NewConfigurationFromAssets(path, assets string) (*Configuration, error) {
	return newConfiguration(path, assets)
}

func newConfiguration(path string, assets string) (conf *Configuration, err error) {
141
	conf = &Configuration{
142
143
144
		Path:           path,
		RevocationPath: filepath.Join(DefaultDataPath(), "revocation"),
		assets:         assets,
145
	}
146
	conf.RevocationStorage = &RevocationStorage{conf: conf}
147

148
149
150
151
	if conf.assets != "" { // If an assets folder is specified, then it must exist
		if err = fs.AssertPathExists(conf.assets); err != nil {
			return nil, errors.WrapPrefix(err, "Nonexistent assets folder specified", 0)
		}
152
	}
153
	if err = fs.EnsureDirectoryExists(conf.Path); err != nil {
154
155
		return nil, err
	}
156

157
158
159
	// Init all maps
	conf.clear()

160
161
162
	return
}

163
func (conf *Configuration) clear() {
164
165
166
	conf.SchemeManagers = make(map[SchemeManagerIdentifier]*SchemeManager)
	conf.Issuers = make(map[IssuerIdentifier]*Issuer)
	conf.CredentialTypes = make(map[CredentialTypeIdentifier]*CredentialType)
167
	conf.AttributeTypes = make(map[AttributeTypeIdentifier]*AttributeType)
168
	conf.DisabledSchemeManagers = make(map[SchemeManagerIdentifier]*SchemeManagerError)
169
	conf.kssPublicKeys = make(map[SchemeManagerIdentifier]map[int]*rsa.PublicKey)
170
	conf.publicKeys = make(map[IssuerIdentifier]map[int]*gabi.PublicKey)
171
	conf.privateKeys = make(map[IssuerIdentifier]*gabi.PrivateKey)
172
	conf.reverseHashes = make(map[string]CredentialTypeIdentifier)
173
174
175
176
177
178
179
}

// ParseFolder populates the current Configuration by parsing the storage path,
// listing the containing scheme managers, issuers and credential types.
func (conf *Configuration) ParseFolder() (err error) {
	// Init all maps
	conf.clear()
180

181
182
	// Copy any new or updated scheme managers out of the assets into storage
	if conf.assets != "" {
183
		err = fs.IterateSubfolders(conf.assets, func(dir string, _ os.FileInfo) error {
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
			scheme := NewSchemeManagerIdentifier(filepath.Base(dir))
			uptodate, err := conf.isUpToDate(scheme)
			if err != nil {
				return err
			}
			if !uptodate {
				_, err = conf.CopyManagerFromAssets(scheme)
			}
			return err
		})
		if err != nil {
			return err
		}
	}

	// Parse scheme managers in storage
200
	var mgrerr *SchemeManagerError
201
	err = fs.IterateSubfolders(conf.Path, func(dir string, _ os.FileInfo) error {
202
203
		manager := NewSchemeManager(filepath.Base(dir))
		err := conf.ParseSchemeManagerFolder(dir, manager)
204
205
		if err == nil {
			return nil // OK, do next scheme manager folder
206
		}
207
208
209
210
		// If there is an error, and it is of type SchemeManagerError, return nil
		// so as to continue parsing other managers.
		var ok bool
		if mgrerr, ok = err.(*SchemeManagerError); ok {
211
			conf.DisabledSchemeManagers[manager.Identifier()] = mgrerr
212
			return nil
213
		}
214
		return err // Not a SchemeManagerError? return it & halt parsing now
215
216
	})
	if err != nil {
217
		return
218
	}
219
	conf.initialized = true
220
221
222
	if mgrerr != nil {
		return mgrerr
	}
223
	return
224
225
}

226
227
228
229
230
231
232
// ParseOrRestoreFolder parses the irma_configuration folder, and when possible attempts to restore
// any broken scheme managers from their remote.
// Any error encountered during parsing is considered recoverable only if it is of type *SchemeManagerError;
// In this case the scheme in which it occured is downloaded from its remote and re-parsed.
// If any other error is encountered at any time, it is returned immediately.
// If no error is returned, parsing and possibly restoring has been succesfull, and there should be no
// disabled scheme managers.
233
234
func (conf *Configuration) ParseOrRestoreFolder() error {
	err := conf.ParseFolder()
235
236
237
	// Only in case of a *SchemeManagerError might we be able to recover
	if _, isSchemeMgrErr := err.(*SchemeManagerError); !isSchemeMgrErr {
		return err
238
	}
239
240
241
	if err != nil && (conf.assets == "" || conf.readOnly) {
		return err
	}
242
243

	for id := range conf.DisabledSchemeManagers {
244
245
246
247
248
249
250
251
252
		if err = conf.ReinstallSchemeManager(conf.SchemeManagers[id]); err == nil {
			continue
		}
		if _, err = conf.CopyManagerFromAssets(id); err != nil {
			return err // File system error, too serious, bail out now
		}
		name := id.String()
		if err = conf.ParseSchemeManagerFolder(filepath.Join(conf.Path, name), NewSchemeManager(name)); err == nil {
			delete(conf.DisabledSchemeManagers, id)
253
		}
254
	}
255

256
257
258
	return err
}

259
// ParseSchemeManagerFolder parses the entire tree of the specified scheme manager
260
// If err != nil then a problem occured
261
func (conf *Configuration) ParseSchemeManagerFolder(dir string, manager *SchemeManager) (err error) {
262
263
264
265
266
267
268
	// From this point, keep it in our map even if it has an error. The user must check either:
	// - manager.Status == SchemeManagerStatusValid, aka "VALID"
	// - or equivalently, manager.Valid == true
	// before using any scheme manager for anything, and handle accordingly
	conf.SchemeManagers[manager.Identifier()] = manager

	// Ensure we return a SchemeManagerError when any error occurs
269
270
	defer func() {
		if err != nil {
271
272
273
274
275
			err = &SchemeManagerError{
				Manager: manager.Identifier(),
				Err:     err,
				Status:  manager.Status,
			}
276
277
278
		}
	}()

279
280
	// Verify signature and read scheme manager description
	if err = conf.VerifySignature(manager.Identifier()); err != nil {
281
282
		return
	}
283
	if manager.index, err = conf.parseIndex(filepath.Base(dir), manager); err != nil {
284
285
		manager.Status = SchemeManagerStatusInvalidIndex
		return
286
	}
287
	exists, err := conf.pathToDescription(manager, dir+"/description.xml", manager)
288
289
290
	if err != nil {
		manager.Status = SchemeManagerStatusParsingError
		return
291
	}
292
293
294
295
	if !exists {
		manager.Status = SchemeManagerStatusParsingError
		return errors.New("Scheme manager description not found")
	}
296
	if err = conf.validateScheme(manager, dir); err != nil {
297
		return
298
	}
299
300
301
302
303
304
305

	// Verify that all other files are validly signed
	err = conf.VerifySchemeManager(manager)
	if err != nil {
		manager.Status = SchemeManagerStatusInvalidSignature
		return
	}
306

307
	// Read timestamp indicating time of last modification
308
309
310
	ts, exists, err := readTimestamp(dir + "/timestamp")
	if err != nil || !exists {
		return errors.WrapPrefix(err, "Could not read scheme manager timestamp", 0)
311
	}
312
	manager.Timestamp = *ts
313

314
	// Parse contained issuers and credential types
315
	err = conf.parseIssuerFolders(manager, dir)
316
317
318
319
320
321
	if err != nil {
		manager.Status = SchemeManagerStatusContentParsingError
		return
	}
	manager.Status = SchemeManagerStatusValid
	manager.Valid = true
322
323
324
	return
}

325
// PrivateKey returns the latest private key of the specified issuer, or nil if not present in the Configuration.
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
func (conf *Configuration) PrivateKey(id IssuerIdentifier) (*gabi.PrivateKey, error) {
	if sk := conf.privateKeys[id]; sk != nil {
		return sk, nil
	}

	path := fmt.Sprintf(privkeyPattern, conf.Path, id.SchemeManagerIdentifier().Name(), id.Name())
	files, err := filepath.Glob(path)
	if err != nil {
		return nil, err
	}
	if len(files) == 0 {
		return nil, nil
	}

	// List private keys and get highest counter
	counters := make([]int, 0, len(files))
	for _, file := range files {
		filename := filepath.Base(file)
		count := filename[:len(filename)-4]
		i, err := strconv.Atoi(count)
		if err != nil {
			return nil, err
		}
		counters = append(counters, i)
	}
	sort.Ints(counters)
	counter := counters[len(counters)-1]

	// Read private key
	file := strings.Replace(path, "*", strconv.Itoa(counter), 1)
	sk, err := gabi.NewPrivateKeyFromFile(file)
	if err != nil {
		return nil, err
	}
	if int(sk.Counter) != counter {
		return nil, errors.Errorf("Private key %s of issuer %s has wrong <Counter>", file, id.String())
	}
	conf.privateKeys[id] = sk

	return sk, nil
}

368
369
// PublicKey returns the specified public key, or nil if not present in the Configuration.
func (conf *Configuration) PublicKey(id IssuerIdentifier, counter int) (*gabi.PublicKey, error) {
370
371
372
373
374
375
376
377
378
379
380
	var haveIssuer, haveKey bool
	var err error
	_, haveIssuer = conf.publicKeys[id]
	if haveIssuer {
		_, haveKey = conf.publicKeys[id][counter]
	}

	// If we have not seen this issuer or key before in conf.publicKeys,
	// try to parse the public key folder; new keys might have been put there since we last parsed it
	if !haveIssuer || !haveKey {
		if err = conf.parseKeysFolder(id); err != nil {
381
			return nil, err
382
383
		}
	}
384
	return conf.publicKeys[id][counter], nil
385
386
}

387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
// KeyshareServerKeyFunc returns a function that returns the public key with which to verify a keyshare server JWT,
// suitable for passing to jwt.Parse() and jwt.ParseWithClaims().
func (conf *Configuration) KeyshareServerKeyFunc(scheme SchemeManagerIdentifier) func(t *jwt.Token) (interface{}, error) {
	return func(t *jwt.Token) (i interface{}, e error) {
		var kid int
		if kidstr, ok := t.Header["kid"].(string); ok {
			var err error
			if kid, err = strconv.Atoi(kidstr); err != nil {
				return nil, err
			}
		}
		return conf.KeyshareServerPublicKey(scheme, kid)
	}
}

// KeyshareServerPublicKey returns the i'th public key of the specified scheme.
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
func (conf *Configuration) KeyshareServerPublicKey(scheme SchemeManagerIdentifier, i int) (*rsa.PublicKey, error) {
	if _, contains := conf.kssPublicKeys[scheme]; !contains {
		conf.kssPublicKeys[scheme] = make(map[int]*rsa.PublicKey)
	}
	if _, contains := conf.kssPublicKeys[scheme][i]; !contains {
		pkbts, err := ioutil.ReadFile(filepath.Join(conf.Path, scheme.Name(), fmt.Sprintf("kss-%d.pem", i)))
		if err != nil {
			return nil, err
		}
		pkblk, _ := pem.Decode(pkbts)
		genericPk, err := x509.ParsePKIXPublicKey(pkblk.Bytes)
		if err != nil {
			return nil, err
		}
		pk, ok := genericPk.(*rsa.PublicKey)
		if !ok {
			return nil, errors.New("Invalid keyshare server public key")
		}
		conf.kssPublicKeys[scheme][i] = pk
	}
	return conf.kssPublicKeys[scheme][i], nil
}

426
func (conf *Configuration) addReverseHash(credid CredentialTypeIdentifier) {
427
	hash := sha256.Sum256([]byte(credid.String()))
428
	conf.reverseHashes[base64.StdEncoding.EncodeToString(hash[:16])] = credid
429
430
}

431
432
433
func (conf *Configuration) hashToCredentialType(hash []byte) *CredentialType {
	if str, exists := conf.reverseHashes[base64.StdEncoding.EncodeToString(hash)]; exists {
		return conf.CredentialTypes[str]
434
435
436
437
	}
	return nil
}

438
// IsInitialized indicates whether this instance has successfully been initialized.
439
440
func (conf *Configuration) IsInitialized() bool {
	return conf.initialized
441
442
}

443
444
445
446
447
448
449
450
451
// Prune removes any invalid scheme managers and everything they own from this Configuration
func (conf *Configuration) Prune() {
	for _, manager := range conf.SchemeManagers {
		if !manager.Valid {
			_ = conf.RemoveSchemeManager(manager.Identifier(), false) // does not return errors
		}
	}
}

452
func (conf *Configuration) parseIssuerFolders(manager *SchemeManager, path string) error {
453
	return fs.IterateSubfolders(path, func(dir string, _ os.FileInfo) error {
454
		issuer := &Issuer{}
455
		exists, err := conf.pathToDescription(manager, dir+"/description.xml", issuer)
456
457
458
		if err != nil {
			return err
		}
459
460
		if !exists {
			return nil
461
		}
462
463
464
		if issuer.XMLVersion < 4 {
			return errors.New("Unsupported issuer description")
		}
465

466
		if err = conf.validateIssuer(manager, issuer, dir); err != nil {
467
468
469
			return err
		}

470
		conf.Issuers[issuer.Identifier()] = issuer
471
		issuer.Valid = conf.SchemeManagers[issuer.SchemeManagerIdentifier()].Valid
472
		return conf.parseCredentialsFolder(manager, issuer, dir+"/Issues/")
473
474
475
	})
}

476
477
478
func (conf *Configuration) DeleteSchemeManager(id SchemeManagerIdentifier) error {
	delete(conf.SchemeManagers, id)
	delete(conf.DisabledSchemeManagers, id)
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
	name := id.String()
	for iss := range conf.Issuers {
		if iss.Root() == name {
			delete(conf.Issuers, iss)
		}
	}
	for iss := range conf.publicKeys {
		if iss.Root() == name {
			delete(conf.publicKeys, iss)
		}
	}
	for cred := range conf.CredentialTypes {
		if cred.Root() == name {
			delete(conf.CredentialTypes, cred)
		}
	}
495
496
497
498
	if !conf.readOnly {
		return os.RemoveAll(filepath.Join(conf.Path, id.Name()))
	}
	return nil
499
500
}

501
// parse $schememanager/$issuer/PublicKeys/$i.xml for $i = 1, ...
502
503
func (conf *Configuration) parseKeysFolder(issuerid IssuerIdentifier) error {
	manager := conf.SchemeManagers[issuerid.SchemeManagerIdentifier()]
504
	conf.publicKeys[issuerid] = map[int]*gabi.PublicKey{}
505
	path := fmt.Sprintf(pubkeyPattern, conf.Path, issuerid.SchemeManagerIdentifier().Name(), issuerid.Name())
506
507
508
509
510
511
512
513
514
515
	files, err := filepath.Glob(path)
	if err != nil {
		return err
	}

	for _, file := range files {
		filename := filepath.Base(file)
		count := filename[:len(filename)-4]
		i, err := strconv.Atoi(count)
		if err != nil {
516
			return err
517
		}
518
		relativepath, err := filepath.Rel(conf.Path, file)
519
520
521
522
		if err != nil {
			return err
		}
		bts, found, err := conf.ReadAuthenticatedFile(manager, relativepath)
523
		if err != nil || !found {
524
525
526
			return err
		}
		pk, err := gabi.NewPublicKeyFromBytes(bts)
527
528
529
		if err != nil {
			return err
		}
530
531
532
		if int(pk.Counter) != i {
			return errors.Errorf("Public key %s of issuer %s has wrong <Counter>", file, issuerid.String())
		}
533
		pk.Issuer = issuerid.String()
534
		conf.publicKeys[issuerid][i] = pk
535
	}
536

537
538
539
	return nil
}

540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
func (conf *Configuration) PublicKeyIndices(issuerid IssuerIdentifier) (i []int, err error) {
	return conf.matchKeyPattern(issuerid, pubkeyPattern)
}

func (conf *Configuration) matchKeyPattern(issuerid IssuerIdentifier, pattern string) (i []int, err error) {
	pkpath := fmt.Sprintf(pattern, conf.Path, issuerid.SchemeManagerIdentifier().Name(), issuerid.Name())
	files, err := filepath.Glob(pkpath)
	if err != nil {
		return
	}
	for _, file := range files {
		var count int
		base := filepath.Base(file)
		if count, err = strconv.Atoi(base[:len(base)-4]); err != nil {
			return
		}
		i = append(i, count)
	}
	sort.Ints(i)
	return
}

562
// parse $schememanager/$issuer/Issues/*/description.xml
563
564
func (conf *Configuration) parseCredentialsFolder(manager *SchemeManager, issuer *Issuer, path string) error {
	var foundcred bool
565
	err := fs.IterateSubfolders(path, func(dir string, _ os.FileInfo) error {
566
		cred := &CredentialType{}
567
		exists, err := conf.pathToDescription(manager, dir+"/description.xml", cred)
568
569
570
		if err != nil {
			return err
		}
571
572
573
		if !exists {
			return nil
		}
574
		if err = conf.validateCredentialType(manager, issuer, cred, dir); err != nil {
575
576
577
578
			return err
		}
		foundcred = true
		cred.Valid = conf.SchemeManagers[cred.SchemeManagerIdentifier()].Valid
579
		credid := cred.Identifier()
580
581
		conf.CredentialTypes[credid] = cred
		conf.addReverseHash(credid)
582
		for index, attr := range cred.AttributeTypes {
583
584
585
586
			attr.Index = index
			attr.SchemeManagerID = cred.SchemeManagerID
			attr.IssuerID = cred.IssuerID
			attr.CredentialTypeID = cred.ID
587
			conf.AttributeTypes[attr.GetAttributeTypeIdentifier()] = attr
588
		}
589
590
		return nil
	})
591
592
593
594
595
596
	if !foundcred {
		conf.Warnings = append(conf.Warnings, fmt.Sprintf("Issuer %s has no credential types", issuer.Identifier().String()))
	}
	return err
}

597
func (conf *Configuration) pathToDescription(manager *SchemeManager, path string, description interface{}) (bool, error) {
598
599
600
601
	if _, err := os.Stat(path); err != nil {
		return false, nil
	}

602
	relativepath, err := filepath.Rel(conf.Path, path)
603
604
605
606
	if err != nil {
		return false, err
	}
	bts, found, err := conf.ReadAuthenticatedFile(manager, relativepath)
607
	if !found {
608
609
		if manager.index.Scheme() != manager.Identifier() {
			return false, errors.Errorf("Folder must be called %s, not %s", manager.index.Scheme(), manager.ID)
610
		}
611
		return false, errors.Errorf("File %s not present in scheme index", relativepath)
612
	}
613
614
615
616
	if err != nil {
		return true, err
	}

617
	err = xml.Unmarshal(bts, description)
618
619
620
621
622
623
	if err != nil {
		return true, err
	}

	return true, nil
}
624

625
626
// ContainsCredentialType checks if the configuration contains the specified credential type.
func (conf *Configuration) ContainsCredentialType(cred CredentialTypeIdentifier) bool {
627
628
629
	return conf.SchemeManagers[cred.IssuerIdentifier().SchemeManagerIdentifier()] != nil &&
		conf.Issuers[cred.IssuerIdentifier()] != nil &&
		conf.CredentialTypes[cred] != nil
630
}
631

632
633
634
635
636
func (conf *Configuration) ContainsAttributeType(attr AttributeTypeIdentifier) bool {
	_, contains := conf.AttributeTypes[attr]
	return contains && conf.ContainsCredentialType(attr.CredentialTypeIdentifier())
}

637
func (conf *Configuration) isUpToDate(scheme SchemeManagerIdentifier) (bool, error) {
638
	if conf.assets == "" || conf.readOnly {
639
640
		return true, nil
	}
641
642
643
644
	name := scheme.String()
	newTime, exists, err := readTimestamp(filepath.Join(conf.assets, name, "timestamp"))
	if err != nil || !exists {
		return true, errors.WrapPrefix(err, "Could not read asset timestamp of scheme "+name, 0)
645
	}
646
647
648
649
	// The storage version of the manager does not need to have a timestamp. If it does not, it is outdated.
	oldTime, exists, err := readTimestamp(filepath.Join(conf.Path, name, "timestamp"))
	if err != nil {
		return true, err
650
	}
651
	return exists && !newTime.After(*oldTime), nil
652
653
}

654
func (conf *Configuration) CopyManagerFromAssets(scheme SchemeManagerIdentifier) (bool, error) {
655
	if conf.assets == "" || conf.readOnly {
656
		return false, nil
657
	}
658
659
660
661
662
	// Remove old version; we want an exact copy of the assets version
	// not a merge of the assets version and the storage version
	name := scheme.String()
	if err := os.RemoveAll(filepath.Join(conf.Path, name)); err != nil {
		return false, err
663
	}
664
665
666
	return true, fs.CopyDirectory(
		filepath.Join(conf.assets, name),
		filepath.Join(conf.Path, name),
667
668
669
	)
}

670
671
// DownloadSchemeManager downloads and returns a scheme manager description.xml file
// from the specified URL.
672
func DownloadSchemeManager(url string) (*SchemeManager, error) {
673
674
675
676
677
678
679
680
681
	if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
		url = "https://" + url
	}
	if url[len(url)-1] == '/' {
		url = url[:len(url)-1]
	}
	if strings.HasSuffix(url, "/description.xml") {
		url = url[:len(url)-len("/description.xml")]
	}
682
	b, err := NewHTTPTransport(url).GetBytes("description.xml")
683
684
685
	if err != nil {
		return nil, err
	}
686
	manager := NewSchemeManager("")
687
688
689
690
691
692
693
694
	if err = xml.Unmarshal(b, manager); err != nil {
		return nil, err
	}

	manager.URL = url // TODO?
	return manager, nil
}

Sietse Ringers's avatar
Sietse Ringers committed
695
696
// RemoveSchemeManager removes the specified scheme manager and all associated issuers,
// public keys and credential types from this Configuration.
697
func (conf *Configuration) RemoveSchemeManager(id SchemeManagerIdentifier, fromStorage bool) error {
698
	// Remove everything falling under the manager's responsibility
699
	for credid := range conf.CredentialTypes {
700
		if credid.IssuerIdentifier().SchemeManagerIdentifier() == id {
701
			delete(conf.CredentialTypes, credid)
702
703
		}
	}
704
	for issid := range conf.Issuers {
705
		if issid.SchemeManagerIdentifier() == id {
706
			delete(conf.Issuers, issid)
707
708
		}
	}
709
	for issid := range conf.publicKeys {
710
		if issid.SchemeManagerIdentifier() == id {
711
			delete(conf.publicKeys, issid)
712
713
		}
	}
714
	delete(conf.SchemeManagers, id)
715

716
	if fromStorage || !conf.readOnly {
Sietse Ringers's avatar
Sietse Ringers committed
717
		return os.RemoveAll(fmt.Sprintf("%s/%s", conf.Path, id.String()))
718
719
	}
	return nil
720
721
}

722
func (conf *Configuration) ReinstallSchemeManager(manager *SchemeManager) (err error) {
723
724
725
726
	if conf.readOnly {
		return errors.New("cannot install scheme into a read-only configuration")
	}

727
728
729
730
731
732
733
734
735
	// Check if downloading stuff from the remote works before we uninstall the specified manager:
	// If we can't download anything we should keep the broken version
	manager, err = DownloadSchemeManager(manager.URL)
	if err != nil {
		return
	}
	if err = conf.DeleteSchemeManager(manager.Identifier()); err != nil {
		return
	}
736
	err = conf.InstallSchemeManager(manager, nil)
737
738
739
	return
}

740
// InstallSchemeManager downloads and adds the specified scheme manager to this Configuration,
Sietse Ringers's avatar
Sietse Ringers committed
741
// provided its signature is valid.
742
func (conf *Configuration) InstallSchemeManager(manager *SchemeManager, publickey []byte) error {
743
744
745
746
	if conf.readOnly {
		return errors.New("cannot install scheme into a read-only configuration")
	}

747
	name := manager.ID
748
	if err := fs.EnsureDirectoryExists(filepath.Join(conf.Path, name)); err != nil {
749
750
		return err
	}
751
752

	t := NewHTTPTransport(manager.URL)
Sietse Ringers's avatar
Sietse Ringers committed
753
	path := fmt.Sprintf("%s/%s", conf.Path, name)
754
755
756
	if err := t.GetFile("description.xml", path+"/description.xml"); err != nil {
		return err
	}
757
758
759
760
761
762
763
764
	if publickey != nil {
		if err := fs.SaveFile(path+"/pk.pem", publickey); err != nil {
			return err
		}
	} else {
		if err := t.GetFile("pk.pem", path+"/pk.pem"); err != nil {
			return err
		}
765
	}
766
	if err := conf.DownloadSchemeManagerSignature(manager); err != nil {
767
768
		return err
	}
769
770
771
772
	conf.SchemeManagers[manager.Identifier()] = manager
	if err := conf.UpdateSchemeManager(manager.Identifier(), nil); err != nil {
		return err
	}
773

774
	return conf.ParseSchemeManagerFolder(filepath.Join(conf.Path, name), manager)
775
776
777
778
779
}

// DownloadSchemeManagerSignature downloads, stores and verifies the latest version
// of the index file and signature of the specified manager.
func (conf *Configuration) DownloadSchemeManagerSignature(manager *SchemeManager) (err error) {
780
781
782
783
	if conf.readOnly {
		return errors.New("cannot download into a read-only configuration")
	}

784
	t := NewHTTPTransport(manager.URL)
Sietse Ringers's avatar
Sietse Ringers committed
785
	path := fmt.Sprintf("%s/%s", conf.Path, manager.ID)
786
787
788
789
	index := filepath.Join(path, "index")
	sig := filepath.Join(path, "index.sig")

	if err = t.GetFile("index", index); err != nil {
790
		return
791
792
	}
	if err = t.GetFile("index.sig", sig); err != nil {
793
		return
794
	}
795
	err = conf.VerifySignature(manager.Identifier())
796
	return
797
}
798

799
func (e *UnknownIdentifierError) Error() string {
Leon's avatar
Leon committed
800
801
802
803
804
	return "Unknown identifiers: " + e.Missing.String()
}

func (e *RequiredAttributeMissingError) Error() string {
	return "Required attributes are missing: " + e.Missing.String()
805
806
}

Sietse Ringers's avatar
Sietse Ringers committed
807
// Download downloads the issuers, credential types and public keys specified in set
Leon's avatar
Leon committed
808
// if the current Configuration does not already have them, and checks their authenticity
Sietse Ringers's avatar
Sietse Ringers committed
809
// using the scheme manager index.
810
func (conf *Configuration) Download(session SessionRequest) (downloaded *IrmaIdentifierSet, err error) {
811
	if conf.readOnly {
Leon's avatar
Leon committed
812
		return nil, errors.New("Cannot download into a read-only configuration")
813
	}
814

Leon's avatar
Leon committed
815
	missing, requiredMissing, err := conf.checkIdentifiers(session)
816
817
	if err != nil {
		return nil, err
818
	}
819
820
	if len(missing.SchemeManagers) > 0 {
		return nil, &UnknownIdentifierError{ErrorUnknownSchemeManager, missing}
821
822
	}

Leon's avatar
Leon committed
823
	// Update the scheme found above and parse, if necessary
824
	downloaded = newIrmaIdentifierSet()
Leon's avatar
Leon committed
825
826
827
828
829
830
831
832
833
834

	// Combine to find all identifiers that possibly require updating, i.e.,
	// ones that are not found in the configuration or,
	// ones that were tagged non-optional, but were tagged optional in a more recent configuration
	allMissing := newIrmaIdentifierSet()
	allMissing.join(missing)
	allMissing.join(requiredMissing)

	// Try updating them
	for id := range allMissing.allSchemes() {
835
		if err = conf.UpdateSchemeManager(id, downloaded); err != nil {
836
837
838
839
			return
		}
	}
	if !downloaded.Empty() {
840
841
842
		if err = conf.ParseFolder(); err != nil {
			return nil, err
		}
843
	}
844

Leon's avatar
Leon committed
845
846
847
	// Check again if all session identifiers are known now and required attributes are present
	missing, requiredMissing, err = conf.checkIdentifiers(session)
	if err != nil {
848
849
		return nil, err
	}
Leon's avatar
Leon committed
850
851

	// Required in the request, but not found in the configuration
852
853
854
855
	if !missing.Empty() {
		return nil, &UnknownIdentifierError{ErrorUnknownIdentifier, missing}
	}

Leon's avatar
Leon committed
856
857
858
859
860
	// (Still) required in the configuration, but not in the request
	if !requiredMissing.Empty() {
		return nil, &RequiredAttributeMissingError{ErrorRequiredAttributeMissing, requiredMissing}
	}

861
862
863
	return
}

Leon's avatar
Leon committed
864
func (conf *Configuration) checkCredentialTypes(session SessionRequest, missing *IrmaIdentifierSet, requiredMissing *IrmaIdentifierSet) {
865
866
867
868
869
870
	var typ *CredentialType
	var contains bool

	switch s := session.(type) {
	case *IssuanceRequest:
		for _, credreq := range s.Credentials {
Leon's avatar
Leon committed
871

872
			// First check if we have this credential type
873
			typ, contains = conf.CredentialTypes[credreq.CredentialTypeID]
874
			if !contains {
875
				missing.CredentialTypes[credreq.CredentialTypeID] = struct{}{}
876
877
				continue
			}
Leon's avatar
Leon committed
878
879
880
881
882
883

			// Check for attributes in the request that are not in the credential configuration
			for reqAttr, _ := range credreq.Attributes {
				attrID := NewAttributeTypeIdentifier(credreq.CredentialTypeID.String() + "." + reqAttr)
				if !typ.ContainsAttribute(attrID) {
					missing.AttributeTypes[attrID] = struct{}{}
884
885
				}
			}
Leon's avatar
Leon committed
886
887
888
889
890
891
892

			// Check if all attributes from the configuration are present, unless they are marked as optional
			for _, attrtype := range typ.AttributeTypes {
				_, present := credreq.Attributes[attrtype.ID]
				if !present && !attrtype.IsOptional() {
					requiredMissing.AttributeTypes[attrtype.GetAttributeTypeIdentifier()] = struct{}{}
				}
893
894
895
896
			}
		}
	}

897
898
899
	_ = session.Disclosure().Disclose.Iterate(func(attr *AttributeRequest) error {
		credid := attr.Type.CredentialTypeIdentifier()
		if typ, contains = conf.CredentialTypes[credid]; !contains {
900
			missing.CredentialTypes[credid] = struct{}{}
901
			return nil
902
		}
903
		if !attr.Type.IsCredential() && !typ.ContainsAttribute(attr.Type) {
Leon's avatar
Leon committed
904
			missing.AttributeTypes[attr.Type] = struct{}{}
905
906
907
		}
		return nil
	})
908

909
910
911
	return
}

Leon's avatar
Leon committed
912
func (conf *Configuration) checkIdentifiers(session SessionRequest) (*IrmaIdentifierSet, *IrmaIdentifierSet, error) {
913
	missing := newIrmaIdentifierSet()
Leon's avatar
Leon committed
914
	requiredMissing := newIrmaIdentifierSet()
915
916
	conf.checkSchemes(session, missing)
	if err := conf.checkIssuers(session.Identifiers(), missing); err != nil {
Leon's avatar
Leon committed
917
		return nil, nil, err
918
	}
Leon's avatar
Leon committed
919
920
	conf.checkCredentialTypes(session, missing, requiredMissing)
	return missing, requiredMissing, nil
921
922
923
924
925
926
927
928
929
930
931
}

// CheckSchemes verifies that all schemes occuring in the specified session request occur in this
// instance.
func (conf *Configuration) checkSchemes(session SessionRequest, missing *IrmaIdentifierSet) {
	for id := range session.Identifiers().SchemeManagers {
		scheme, contains := conf.SchemeManagers[id]
		if !contains || !scheme.Valid {
			missing.SchemeManagers[id] = struct{}{}
		}
	}
932
933
}

934
func (conf *Configuration) checkIssuers(set *IrmaIdentifierSet, missing *IrmaIdentifierSet) error {
935
	for issid := range set.Issuers {
936
		if _, contains := conf.Issuers[issid]; !contains {
937
			missing.Issuers[issid] = struct{}{}
938
		}
Sietse Ringers's avatar
Sietse Ringers committed
939
	}
940
941
942
	for issid, keyids := range set.PublicKeys {
		for _, keyid := range keyids {
			pk, err := conf.PublicKey(issid, keyid)
Sietse Ringers's avatar
Sietse Ringers committed
943
			if err != nil {
944
				return err
Sietse Ringers's avatar
Sietse Ringers committed
945
946
			}
			if pk == nil {
947
				missing.PublicKeys[issid] = append(missing.PublicKeys[issid], keyid)
948
949
950
			}
		}
	}
951
	return nil
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
}

func (i SchemeManagerIndex) String() string {
	var paths []string
	var b bytes.Buffer

	for path := range i {
		paths = append(paths, path)
	}
	sort.Strings(paths)

	for _, path := range paths {
		b.WriteString(hex.EncodeToString(i[path]))
		b.WriteString(" ")
		b.WriteString(path)
		b.WriteString("\n")
	}

	return b.String()
}

Sietse Ringers's avatar
Sietse Ringers committed
973
// FromString populates this index by parsing the specified string.
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
func (i SchemeManagerIndex) FromString(s string) error {
	for j, line := range strings.Split(s, "\n") {
		if len(line) == 0 {
			continue
		}
		parts := strings.Split(line, " ")
		if len(parts) != 2 {
			return errors.Errorf("Scheme manager index line %d has incorrect amount of parts", j)
		}
		hash, err := hex.DecodeString(parts[0])
		if err != nil {
			return err
		}
		i[parts[1]] = hash
	}

	return nil
}

993
994
995
996
997
998
999
func (i SchemeManagerIndex) Scheme() SchemeManagerIdentifier {
	for p := range i {
		return NewSchemeManagerIdentifier(p[0:strings.Index(p, "/")])
	}
	return NewSchemeManagerIdentifier("")
}

1000
// parseIndex parses the index file of the specified manager.
1001
func (conf *Configuration) parseIndex(name string, manager *SchemeManager) (SchemeManagerIndex, error) {
Sietse Ringers's avatar
Sietse Ringers committed
1002
	path := filepath.Join(conf.Path, name, "index")
Sietse Ringers's avatar
Sietse Ringers committed
1003
	if err := fs.AssertPathExists(path); err != nil {
1004
		return nil, fmt.Errorf("Missing scheme manager index file; tried %s", path)
1005
	}
Sietse Ringers's avatar
Sietse Ringers committed
1006
	indexbts, err := ioutil.ReadFile(path)
1007
	if err != nil {