irmaconfig.go 20 KB
Newer Older
1
package irma
2
3
4
5
6
7
8
9
10

import (
	"encoding/base64"
	"encoding/xml"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"

11
12
	"crypto/sha256"

13
14
15
16
	"fmt"

	"strings"

17
18
19
20
21
22
23
24
25
26
27
28
	"sort"

	"bytes"

	"encoding/hex"

	"crypto/ecdsa"
	"crypto/x509"
	"encoding/asn1"
	"encoding/pem"
	"math/big"

29
	"github.com/credentials/irmago/internal/fs"
30
	"github.com/go-errors/errors"
31
32
33
	"github.com/mhe/gabi"
)

34
// Configuration keeps track of scheme managers, issuers, credential types and public keys,
35
// dezerializing them from an irma_configuration folder, and downloads and saves new ones on demand.
36
type Configuration struct {
37
38
39
	SchemeManagers  map[SchemeManagerIdentifier]*SchemeManager
	Issuers         map[IssuerIdentifier]*Issuer
	CredentialTypes map[CredentialTypeIdentifier]*CredentialType
40

41
42
	DisabledSchemeManagers map[SchemeManagerIdentifier]*SchemeManager

43
	publicKeys    map[IssuerIdentifier]map[int]*gabi.PublicKey
44
	reverseHashes map[string]CredentialTypeIdentifier
45
	initialized   bool
46
47
	path          string
	assets        string
48
49
}

Sietse Ringers's avatar
Sietse Ringers committed
50
51
// ConfigurationFileHash encodes the SHA256 hash of an authenticated
// file under a scheme manager within the configuration folder.
52
53
type ConfigurationFileHash []byte

Sietse Ringers's avatar
Sietse Ringers committed
54
55
// SchemeManagerIndex is a (signed) list of files under a scheme manager
// along with their SHA266 hash
56
57
type SchemeManagerIndex map[string]ConfigurationFileHash

58
59
60
61
62
63
64
65
66
type SchemeManagerError struct {
	Manager SchemeManagerIdentifier
	Err     error
}

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

67
// NewConfiguration returns a new configuration. After this
68
// ParseFolder() should be called to parse the specified path.
69
70
func NewConfiguration(path string, assets string) (conf *Configuration, err error) {
	conf = &Configuration{
71
72
		path:   path,
		assets: assets,
73
	}
74

75
	if err = fs.EnsureDirectoryExists(conf.path); err != nil {
76
77
		return nil, err
	}
78
79
	if conf.assets != "" {
		if err = conf.CopyFromAssets(false); err != nil {
80
81
82
83
			return nil, err
		}
	}

84
85
86
	return
}

87
// ParseFolder populates the current Configuration by parsing the storage path,
88
// listing the containing scheme managers, issuers and credential types.
89
func (conf *Configuration) ParseFolder() error {
90
	// Init all maps
91
92
93
	conf.SchemeManagers = make(map[SchemeManagerIdentifier]*SchemeManager)
	conf.Issuers = make(map[IssuerIdentifier]*Issuer)
	conf.CredentialTypes = make(map[CredentialTypeIdentifier]*CredentialType)
94

95
96
	conf.DisabledSchemeManagers = make(map[SchemeManagerIdentifier]*SchemeManager)
	conf.publicKeys = make(map[IssuerIdentifier]map[int]*gabi.PublicKey)
97
	conf.reverseHashes = make(map[string]CredentialTypeIdentifier)
98

99
	var mgrerr *SchemeManagerError
100
	err := iterateSubfolders(conf.path, func(dir string) error {
101
102
103
		err := conf.parseSchemeManagerFolder(dir)
		if err == nil {
			return nil // OK, do next scheme manager folder
104
		}
105
106
107
108
109
		// 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 {
			return nil
110
		}
111
		return err // Not a SchemeManagerError? return it & halt parsing now
112
113
114
115
	})
	if err != nil {
		return err
	}
116
	conf.initialized = true
117
118
119
	if mgrerr != nil {
		return mgrerr
	}
120
121
122
	return nil
}

123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
func (conf *Configuration) parseSchemeManagerFolder(dir string) (err error) {
	exists, err := fs.PathExists(dir + "/description.xml")
	if err != nil || !exists {
		return err
	}

	// Put the directory name in the ID field in case we return early due to errors
	manager := &SchemeManager{ID: filepath.Base(dir)}
	defer func() {
		if err != nil {
			conf.DisabledSchemeManagers[manager.Identifier()] = manager
			err = &SchemeManagerError{Manager: manager.Identifier(), Err: err}
			_ = conf.RemoveSchemeManager(manager.Identifier(), false) // does not return errors
		}
	}()

	if err = conf.parseIndex(filepath.Base(dir), manager); err != nil {
		return err
	}
	_, err = conf.pathToDescription(manager, dir+"/description.xml", manager)
	if err != nil || !exists {
		return err
	}

	if manager.XMLVersion < 7 {
		return errors.New("Unsupported scheme manager description")
	}
	valid, err := conf.VerifySignature(manager.Identifier())
	if err != nil {
		return err
	}
	if !valid {
		return errors.New("Scheme manager signature was invalid")
	}
	conf.SchemeManagers[manager.Identifier()] = manager
	err = conf.parseIssuerFolders(manager, dir)
	return
}

162
163
164
165
func relativePath(absolute string, relative string) string {
	return relative[len(absolute)+1:]
}

166
167
168
169
// 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) {
	if _, contains := conf.publicKeys[id]; !contains {
		conf.publicKeys[id] = map[int]*gabi.PublicKey{}
170
		if err := conf.parseKeysFolder(conf.SchemeManagers[id.SchemeManagerIdentifier()], id); err != nil {
171
			return nil, err
172
173
		}
	}
174
	return conf.publicKeys[id][counter], nil
175
176
}

177
func (conf *Configuration) addReverseHash(credid CredentialTypeIdentifier) {
178
	hash := sha256.Sum256([]byte(credid.String()))
179
	conf.reverseHashes[base64.StdEncoding.EncodeToString(hash[:16])] = credid
180
181
}

182
183
184
func (conf *Configuration) hashToCredentialType(hash []byte) *CredentialType {
	if str, exists := conf.reverseHashes[base64.StdEncoding.EncodeToString(hash)]; exists {
		return conf.CredentialTypes[str]
185
186
187
188
	}
	return nil
}

189
// IsInitialized indicates whether this instance has successfully been initialized.
190
191
func (conf *Configuration) IsInitialized() bool {
	return conf.initialized
192
193
}

194
func (conf *Configuration) parseIssuerFolders(manager *SchemeManager, path string) error {
195
196
	return iterateSubfolders(path, func(dir string) error {
		issuer := &Issuer{}
197
		exists, err := conf.pathToDescription(manager, dir+"/description.xml", issuer)
198
199
200
		if err != nil {
			return err
		}
201
202
		if !exists {
			return nil
203
		}
204
205
206
207
		if issuer.XMLVersion < 4 {
			return errors.New("Unsupported issuer description")
		}
		conf.Issuers[issuer.Identifier()] = issuer
208
		return conf.parseCredentialsFolder(manager, dir+"/Issues/")
209
210
211
	})
}

212
// parse $schememanager/$issuer/PublicKeys/$i.xml for $i = 1, ...
213
func (conf *Configuration) parseKeysFolder(manager *SchemeManager, issuerid IssuerIdentifier) error {
214
	path := fmt.Sprintf("%s/%s/%s/PublicKeys/*.xml", conf.path, issuerid.SchemeManagerIdentifier().Name(), issuerid.Name())
215
216
217
218
219
220
221
222
223
224
225
	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 {
			continue
226
		}
227
228
229
230
231
		bts, err := conf.ReadAuthenticatedFile(manager, relativePath(conf.path, file))
		if err != nil {
			return err
		}
		pk, err := gabi.NewPublicKeyFromBytes(bts)
232
233
234
		if err != nil {
			return err
		}
235
		pk.Issuer = issuerid.String()
236
		conf.publicKeys[issuerid][i] = pk
237
	}
238

239
240
241
	return nil
}

242
// parse $schememanager/$issuer/Issues/*/description.xml
243
func (conf *Configuration) parseCredentialsFolder(manager *SchemeManager, path string) error {
244
245
	return iterateSubfolders(path, func(dir string) error {
		cred := &CredentialType{}
246
		exists, err := conf.pathToDescription(manager, dir+"/description.xml", cred)
247
248
249
		if err != nil {
			return err
		}
250
251
252
253
254
		if !exists {
			return nil
		}
		if cred.XMLVersion < 4 {
			return errors.New("Unsupported credential type description")
255
		}
256
257
258
		credid := cred.Identifier()
		conf.CredentialTypes[credid] = cred
		conf.addReverseHash(credid)
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
		return nil
	})
}

// iterateSubfolders iterates over the subfolders of the specified path,
// calling the specified handler each time. If anything goes wrong, or
// if the caller returns a non-nil error, an error is immediately returned.
func iterateSubfolders(path string, handler func(string) error) error {
	dirs, err := filepath.Glob(path + "/*")
	if err != nil {
		return err
	}

	for _, dir := range dirs {
		stat, err := os.Stat(dir)
		if err != nil {
			return err
		}
		if !stat.IsDir() {
			continue
		}
280
281
282
		if strings.HasSuffix(dir, "/.git") {
			continue
		}
283
284
285
286
287
288
289
290
291
		err = handler(dir)
		if err != nil {
			return err
		}
	}

	return nil
}

292
func (conf *Configuration) pathToDescription(manager *SchemeManager, path string, description interface{}) (bool, error) {
293
294
295
296
	if _, err := os.Stat(path); err != nil {
		return false, nil
	}

297
	bts, err := conf.ReadAuthenticatedFile(manager, relativePath(conf.path, path))
298
299
300
301
	if err != nil {
		return true, err
	}

302
	err = xml.Unmarshal(bts, description)
303
304
305
306
307
308
	if err != nil {
		return true, err
	}

	return true, nil
}
309

310
311
312
313
314
// Contains checks if the configuration contains the specified credential type.
func (conf *Configuration) Contains(cred CredentialTypeIdentifier) bool {
	return conf.SchemeManagers[cred.IssuerIdentifier().SchemeManagerIdentifier()] != nil &&
		conf.Issuers[cred.IssuerIdentifier()] != nil &&
		conf.CredentialTypes[cred] != nil
315
}
316

317
318
319
// CopyFromAssets recursively copies the directory tree from the assets folder
// into the directory of this Configuration.
func (conf *Configuration) CopyFromAssets(parse bool) error {
320
	if err := fs.EnsureDirectoryExists(conf.path); err != nil {
321
322
323
		return err
	}

324
	err := filepath.Walk(conf.assets, filepath.WalkFunc(
325
		func(path string, info os.FileInfo, err error) error {
326
			if path == conf.assets {
327
328
				return nil
			}
329
			subpath := path[len(conf.assets):]
330
			if info.IsDir() {
331
				if err := fs.EnsureDirectoryExists(conf.path + subpath); err != nil {
332
333
334
335
336
337
338
339
					return err
				}
			} else {
				srcfile, err := os.Open(path)
				if err != nil {
					return err
				}
				defer srcfile.Close()
340
				bts, err := ioutil.ReadAll(srcfile)
Sietse Ringers's avatar
Sietse Ringers committed
341
342
343
				if err != nil {
					return err
				}
344
				if err := fs.SaveFile(conf.path+subpath, bts); err != nil {
345
346
347
348
349
350
					return err
				}
			}
			return nil
		}),
	)
351
352
353
354
355

	if err != nil {
		return err
	}
	if parse {
356
		return conf.ParseFolder()
357
358
	}
	return nil
359
}
360

361
362
// DownloadSchemeManager downloads and returns a scheme manager description.xml file
// from the specified URL.
363
func (conf *Configuration) DownloadSchemeManager(url string) (*SchemeManager, error) {
364
365
366
367
368
369
370
371
372
	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")]
	}
373
	b, err := NewHTTPTransport(url).GetBytes("description.xml")
374
375
376
377
378
379
380
381
382
383
384
385
	if err != nil {
		return nil, err
	}
	manager := &SchemeManager{}
	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
386
387
// RemoveSchemeManager removes the specified scheme manager and all associated issuers,
// public keys and credential types from this Configuration.
388
func (conf *Configuration) RemoveSchemeManager(id SchemeManagerIdentifier, fromStorage bool) error {
389
	// Remove everything falling under the manager's responsibility
390
	for credid := range conf.CredentialTypes {
391
		if credid.IssuerIdentifier().SchemeManagerIdentifier() == id {
392
			delete(conf.CredentialTypes, credid)
393
394
		}
	}
395
	for issid := range conf.Issuers {
396
		if issid.SchemeManagerIdentifier() == id {
397
			delete(conf.Issuers, issid)
398
399
		}
	}
400
	for issid := range conf.publicKeys {
401
		if issid.SchemeManagerIdentifier() == id {
402
			delete(conf.publicKeys, issid)
403
404
		}
	}
405
	delete(conf.SchemeManagers, id)
406
407
408
409
410

	if fromStorage {
		return os.RemoveAll(fmt.Sprintf("%s/%s", conf.path, id.String()))
	}
	return nil
411
412
}

Sietse Ringers's avatar
Sietse Ringers committed
413
414
// AddSchemeManager adds the specified scheme manager to this Configuration,
// provided its signature is valid.
415
func (conf *Configuration) AddSchemeManager(manager *SchemeManager) error {
416
	name := manager.ID
417
	if err := fs.EnsureDirectoryExists(fmt.Sprintf("%s/%s", conf.path, name)); err != nil {
418
419
		return err
	}
420
421
422
423
424
425

	t := NewHTTPTransport(manager.URL)
	path := fmt.Sprintf("%s/%s", conf.path, name)
	if err := t.GetFile("description.xml", path+"/description.xml"); err != nil {
		return err
	}
426
	if err := t.GetFile("pk.pem", path+"/pk.pem"); err != nil {
427
428
		return err
	}
429
	if err := conf.DownloadSchemeManagerSignature(manager); err != nil {
430
431
		return err
	}
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455

	conf.SchemeManagers[NewSchemeManagerIdentifier(name)] = manager
	return nil
}

// 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) {
	t := NewHTTPTransport(manager.URL)
	path := fmt.Sprintf("%s/%s", conf.path, manager.ID)
	index := filepath.Join(path, "index")
	sig := filepath.Join(path, "index.sig")

	// Backup so we can restore last valid signature if the new signature is invalid
	if err := conf.backupManagerSignature(index, sig); err != nil {
		return err
	}

	if err = t.GetFile("index", index); err != nil {
		return err
	}
	if err = t.GetFile("index.sig", sig); err != nil {
		return err
	}
Sietse Ringers's avatar
Sietse Ringers committed
456
457
	valid, err := conf.VerifySignature(manager.Identifier())
	if err != nil {
458
		_ = conf.restoreManagerSignature(index, sig)
Sietse Ringers's avatar
Sietse Ringers committed
459
460
461
		return err
	}
	if !valid {
462
		_ = conf.restoreManagerSignature(index, sig)
Sietse Ringers's avatar
Sietse Ringers committed
463
464
		return errors.New("Scheme manager signature invalid")
	}
465

466
467
	return nil
}
468

469
470
471
472
473
474
475
476
477
func (conf *Configuration) backupManagerSignature(index, sig string) error {
	if err := fs.Copy(index, index+".backup"); err != nil {
		return err
	}
	if err := fs.Copy(sig, sig+".backup"); err != nil {
		return err
	}
	return nil
}
478

479
480
func (conf *Configuration) restoreManagerSignature(index, sig string) error {
	if err := fs.Copy(index+".backup", index); err != nil {
481
482
		return err
	}
483
	if err := fs.Copy(sig+".backup", sig); err != nil {
484
485
486
487
488
		return err
	}
	return nil
}

Sietse Ringers's avatar
Sietse Ringers committed
489
490
491
// Download downloads the issuers, credential types and public keys specified in set
// if the current Configuration does not already have them,  and checks their authenticity
// using the scheme manager index.
492
func (conf *Configuration) Download(set *IrmaIdentifierSet) (*IrmaIdentifierSet, error) {
493
	var contains bool
494
495
496
497
498
499
	var err error
	downloaded := &IrmaIdentifierSet{
		SchemeManagers:  map[SchemeManagerIdentifier]struct{}{},
		Issuers:         map[IssuerIdentifier]struct{}{},
		CredentialTypes: map[CredentialTypeIdentifier]struct{}{},
	}
500
	updatedManagers := make(map[SchemeManagerIdentifier]struct{})
501

502
	for manid := range set.SchemeManagers {
503
		if _, contains = conf.SchemeManagers[manid]; !contains {
504
			return nil, errors.Errorf("Unknown scheme manager: %s", manid)
505
506
507
508
509
		}
	}

	transport := NewHTTPTransport("")
	for issid := range set.Issuers {
510
		if _, contains = conf.Issuers[issid]; !contains {
511
512
513
			manager := issid.SchemeManagerIdentifier()
			url := conf.SchemeManagers[manager].URL + "/" + issid.Name()
			path := fmt.Sprintf("%s/%s/%s", conf.path, manager.String(), issid.Name())
514
515
516
			if err = transport.GetFile(url+"/description.xml", path+"/description.xml"); err != nil {
				return nil, err
			}
517
			if err = transport.GetFile(url+"/logo.png", path+"/logo.png"); err != nil {
518
519
				return nil, err
			}
520
			updatedManagers[manager] = struct{}{}
521
			downloaded.Issuers[issid] = struct{}{}
522
		}
Sietse Ringers's avatar
Sietse Ringers committed
523
524
525
	}
	for issid, list := range set.PublicKeys {
		for _, count := range list {
526
			pk, err := conf.PublicKey(issid, count)
Sietse Ringers's avatar
Sietse Ringers committed
527
528
529
530
531
532
			if err != nil {
				return nil, err
			}
			if pk == nil {
				manager := issid.SchemeManagerIdentifier()
				suffix := fmt.Sprintf("/%s/PublicKeys/%d.xml", issid.Name(), count)
533
				path := fmt.Sprintf("%s/%s/%s", conf.path, manager.String(), suffix)
534
				if err = transport.GetFile(conf.SchemeManagers[manager].URL+suffix, path); err != nil {
535
					return nil, err
536
				}
537
				updatedManagers[manager] = struct{}{}
538
539
540
541
			}
		}
	}
	for credid := range set.CredentialTypes {
542
		if _, contains := conf.CredentialTypes[credid]; !contains {
543
544
			issuer := credid.IssuerIdentifier()
			manager := issuer.SchemeManagerIdentifier()
545
			local := fmt.Sprintf("%s/%s/%s/Issues", conf.path, manager.Name(), issuer.Name())
546
			if err := fs.EnsureDirectoryExists(local); err != nil {
547
				return nil, err
548
			}
549
550
			if err = transport.GetFile(
				fmt.Sprintf("%s/%s/Issues/%s/description.xml", conf.SchemeManagers[manager].URL, issuer.Name(), credid.Name()),
551
				fmt.Sprintf("%s/%s/description.xml", local, credid.Name()),
552
553
554
			); err != nil {
				return nil, err
			}
555
556
557
558
			_ = transport.GetFile( // Get logo but ignore errors, it is optional
				fmt.Sprintf("%s/%s/Issues/%s/logo.png", conf.SchemeManagers[manager].URL, issuer.Name(), credid.Name()),
				fmt.Sprintf("%s/%s/logo.png", local, credid.Name()),
			)
559
			updatedManagers[manager] = struct{}{}
560
			downloaded.CredentialTypes[credid] = struct{}{}
561
562
563
		}
	}

564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
	for manager := range updatedManagers {
		if err := conf.DownloadSchemeManagerSignature(conf.SchemeManagers[manager]); err != nil {
			return nil, err
		}
	}
	if !downloaded.Empty() {
		return downloaded, conf.ParseFolder()
	}
	return downloaded, nil
}

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
594
// FromString populates this index by parsing the specified string.
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
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
}

614
615
616
// parseIndex parses the index file of the specified manager.
func (conf *Configuration) parseIndex(name string, manager *SchemeManager) error {
	path := filepath.Join(conf.path, name, "index")
Sietse Ringers's avatar
Sietse Ringers committed
617
	if err := fs.AssertPathExists(path); err != nil {
618
619
		return errors.New("Missing scheme manager index file")
	}
Sietse Ringers's avatar
Sietse Ringers committed
620
	indexbts, err := ioutil.ReadFile(path)
621
622
623
	if err != nil {
		return err
	}
624
625
	manager.Index = make(map[string]ConfigurationFileHash)
	return manager.Index.FromString(string(indexbts))
626
627
628
629
630
631
}

// ReadAuthenticatedFile reads the file at the specified path
// and verifies its authenticity by checking that the file hash
// is present in the (signed) scheme manager index file.
func (conf *Configuration) ReadAuthenticatedFile(manager *SchemeManager, path string) ([]byte, error) {
632
	signedHash, ok := manager.Index[path]
633
634
635
636
637
638
639
640
641
642
643
	if !ok {
		return nil, errors.New("File not present in scheme manager index")
	}

	bts, err := ioutil.ReadFile(filepath.Join(conf.path, path))
	if err != nil {
		return nil, err
	}
	computedHash := sha256.Sum256(bts)

	if !bytes.Equal(computedHash[:], signedHash) {
644
		return nil, errors.Errorf("Hash of %s does not match scheme manager index", path)
645
646
647
648
649
650
651
	}
	return bts, nil
}

// VerifySignature verifies the signature on the scheme manager index file
// (which contains the SHA256 hashes of all files under this scheme manager,
// which are used for verifying file authenticity).
652
653
654
655
656
657
658
659
660
661
662
663
func (conf *Configuration) VerifySignature(id SchemeManagerIdentifier) (valid bool, err error) {
	defer func() {
		if r := recover(); r != nil {
			valid = false
			if e, ok := r.(error); ok {
				err = errors.Errorf("Scheme manager index signature failed to verify: %s", e.Error())
			} else {
				err = errors.New("Scheme manager index signature failed to verify")
			}
		}
	}()

664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
	dir := filepath.Join(conf.path, id.String())
	if err := fs.AssertPathExists(dir+"/index", dir+"/index.sig", dir+"/pk.pem"); err != nil {
		return false, errors.New("Missing scheme manager index file, signature, or public key")
	}

	// Read and hash index file
	indexbts, err := ioutil.ReadFile(dir + "/index")
	if err != nil {
		return false, err
	}
	indexhash := sha256.Sum256(indexbts)

	// Read and parse scheme manager public key
	pkbts, err := ioutil.ReadFile(dir + "/pk.pem")
	if err != nil {
		return false, err
	}
	pkblk, _ := pem.Decode(pkbts)
	genericPk, err := x509.ParsePKIXPublicKey(pkblk.Bytes)
	if err != nil {
		return false, err
	}
	pk, ok := genericPk.(*ecdsa.PublicKey)
	if !ok {
		return false, errors.New("Invalid scheme manager public key")
	}

	// Read and parse signature
	sig, err := ioutil.ReadFile(dir + "/index.sig")
	if err != nil {
		return false, err
	}
	ints := make([]*big.Int, 0, 2)
	_, err = asn1.Unmarshal(sig, &ints)

	// Verify signature
	return ecdsa.Verify(pk, indexhash[:], ints[0], ints[1]), nil
701
}
702
703
704
705

func (hash ConfigurationFileHash) String() string {
	return hex.EncodeToString(hash)
}