irmaconfig.go 22.6 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/go-errors/errors"
30
	"github.com/mhe/gabi"
31
	"github.com/privacybydesign/irmago/internal/fs"
32
33
)

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
43
44
	// 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
	// TODO: what can we say about the consistency of this Configuration if this is not empty?
45

46
	publicKeys    map[IssuerIdentifier]map[int]*gabi.PublicKey
47
	reverseHashes map[string]CredentialTypeIdentifier
48
	initialized   bool
49
50
	path          string
	assets        string
51
52
}

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

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

61
62
type SchemeManagerStatus string

63
64
type SchemeManagerError struct {
	Manager SchemeManagerIdentifier
65
	Status  SchemeManagerStatus
66
67
68
	Err     error
}

69
70
71
72
73
74
75
76
77
const (
	SchemeManagerStatusValid               = SchemeManagerStatus("Valid")
	SchemeManagerStatusUnprocessed         = SchemeManagerStatus("Unprocessed")
	SchemeManagerStatusInvalidIndex        = SchemeManagerStatus("InvalidIndex")
	SchemeManagerStatusInvalidSignature    = SchemeManagerStatus("InvalidSignature")
	SchemeManagerStatusParsingError        = SchemeManagerStatus("ParsingError")
	SchemeManagerStatusContentParsingError = SchemeManagerStatus("ContentParsingError")
)

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

82
// NewConfiguration returns a new configuration. After this
83
// ParseFolder() should be called to parse the specified path.
84
85
func NewConfiguration(path string, assets string) (conf *Configuration, err error) {
	conf = &Configuration{
86
87
		path:   path,
		assets: assets,
88
	}
89

90
	if err = fs.EnsureDirectoryExists(conf.path); err != nil {
91
92
		return nil, err
	}
93
	if conf.assets != "" && fs.Empty(conf.path) {
94
		if err = conf.CopyFromAssets(false); err != nil {
95
96
97
98
			return nil, err
		}
	}

99
100
101
	return
}

102
// ParseFolder populates the current Configuration by parsing the storage path,
103
// listing the containing scheme managers, issuers and credential types.
104
func (conf *Configuration) ParseFolder() (err error) {
105
	// Init all maps
106
107
108
	conf.SchemeManagers = make(map[SchemeManagerIdentifier]*SchemeManager)
	conf.Issuers = make(map[IssuerIdentifier]*Issuer)
	conf.CredentialTypes = make(map[CredentialTypeIdentifier]*CredentialType)
109
	conf.DisabledSchemeManagers = make(map[SchemeManagerIdentifier]*SchemeManagerError)
110
	conf.publicKeys = make(map[IssuerIdentifier]map[int]*gabi.PublicKey)
111
	conf.reverseHashes = make(map[string]CredentialTypeIdentifier)
112

113
	var mgrerr *SchemeManagerError
114
	err = iterateSubfolders(conf.path, func(dir string) error {
115
116
		manager := &SchemeManager{ID: filepath.Base(dir), Status: SchemeManagerStatusUnprocessed, Valid: false}
		err := conf.parseSchemeManagerFolder(dir, manager)
117
118
		if err == nil {
			return nil // OK, do next scheme manager folder
119
		}
120
121
122
123
		// 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 {
124
			conf.DisabledSchemeManagers[manager.Identifier()] = mgrerr
125
			return nil
126
		}
127
		return err // Not a SchemeManagerError? return it & halt parsing now
128
129
	})
	if err != nil {
130
		return
131
	}
132
	conf.initialized = true
133
134
135
	if mgrerr != nil {
		return mgrerr
	}
136
	return
137
138
}

139
140
141
142
143
144
145
146
147
148
149
150
func (conf *Configuration) ParseOrRestoreFolder() error {
	err := conf.ParseFolder()
	var parse bool
	for id := range conf.DisabledSchemeManagers {
		parse = conf.CopyManagerFromAssets(id)
	}
	if parse {
		return conf.ParseFolder()
	}
	return err
}

151
152
// parseSchemeManagerFolder parses the entire tree of the specified scheme manager
// If err != nil then a problem occured
153
func (conf *Configuration) parseSchemeManagerFolder(dir string, manager *SchemeManager) (err error) {
154
155
156
157
158
159
160
	// 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
161
162
	defer func() {
		if err != nil {
163
164
165
166
167
			err = &SchemeManagerError{
				Manager: manager.Identifier(),
				Err:     err,
				Status:  manager.Status,
			}
168
169
170
		}
	}()

171
172
173
174
175
	err = fs.AssertPathExists(dir + "/description.xml")
	if err != nil {
		return
	}

176
	if err = conf.parseIndex(filepath.Base(dir), manager); err != nil {
177
178
		manager.Status = SchemeManagerStatusInvalidIndex
		return
179
	}
180

181
	err = conf.VerifySchemeManager(manager)
182
183
184
185
186
	if err != nil {
		manager.Status = SchemeManagerStatusInvalidSignature
		return
	}

187
188
189
190
191
	exists, err := conf.pathToDescription(manager, dir+"/description.xml", manager)
	if !exists {
		manager.Status = SchemeManagerStatusParsingError
		return errors.New("Scheme manager description not found")
	}
192
193
194
	if err != nil {
		manager.Status = SchemeManagerStatusParsingError
		return
195
196
197
	}

	if manager.XMLVersion < 7 {
198
		manager.Status = SchemeManagerStatusParsingError
199
		return errors.New("Unsupported scheme manager description")
200
	}
201

202
	err = conf.parseIssuerFolders(manager, dir)
203
204
205
206
207
208
	if err != nil {
		manager.Status = SchemeManagerStatusContentParsingError
		return
	}
	manager.Status = SchemeManagerStatusValid
	manager.Valid = true
209
210
211
	return
}

212
213
214
215
func relativePath(absolute string, relative string) string {
	return relative[len(absolute)+1:]
}

216
217
218
219
// 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{}
220
		if err := conf.parseKeysFolder(conf.SchemeManagers[id.SchemeManagerIdentifier()], id); err != nil {
221
			return nil, err
222
223
		}
	}
224
	return conf.publicKeys[id][counter], nil
225
226
}

227
func (conf *Configuration) addReverseHash(credid CredentialTypeIdentifier) {
228
	hash := sha256.Sum256([]byte(credid.String()))
229
	conf.reverseHashes[base64.StdEncoding.EncodeToString(hash[:16])] = credid
230
231
}

232
233
234
func (conf *Configuration) hashToCredentialType(hash []byte) *CredentialType {
	if str, exists := conf.reverseHashes[base64.StdEncoding.EncodeToString(hash)]; exists {
		return conf.CredentialTypes[str]
235
236
237
238
	}
	return nil
}

239
// IsInitialized indicates whether this instance has successfully been initialized.
240
241
func (conf *Configuration) IsInitialized() bool {
	return conf.initialized
242
243
}

244
245
246
247
248
249
250
251
252
// 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
		}
	}
}

253
func (conf *Configuration) parseIssuerFolders(manager *SchemeManager, path string) error {
254
255
	return iterateSubfolders(path, func(dir string) error {
		issuer := &Issuer{}
256
		exists, err := conf.pathToDescription(manager, dir+"/description.xml", issuer)
257
258
259
		if err != nil {
			return err
		}
260
261
		if !exists {
			return nil
262
		}
263
264
265
266
		if issuer.XMLVersion < 4 {
			return errors.New("Unsupported issuer description")
		}
		conf.Issuers[issuer.Identifier()] = issuer
267
		issuer.Valid = conf.SchemeManagers[issuer.SchemeManagerIdentifier()].Valid
268
		return conf.parseCredentialsFolder(manager, dir+"/Issues/")
269
270
271
	})
}

272
// parse $schememanager/$issuer/PublicKeys/$i.xml for $i = 1, ...
273
func (conf *Configuration) parseKeysFolder(manager *SchemeManager, issuerid IssuerIdentifier) error {
274
	path := fmt.Sprintf("%s/%s/%s/PublicKeys/*.xml", conf.path, issuerid.SchemeManagerIdentifier().Name(), issuerid.Name())
275
276
277
278
279
280
281
282
283
284
285
	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
286
		}
287
288
		bts, found, err := conf.ReadAuthenticatedFile(manager, relativePath(conf.path, file))
		if err != nil || !found {
289
290
291
			return err
		}
		pk, err := gabi.NewPublicKeyFromBytes(bts)
292
293
294
		if err != nil {
			return err
		}
295
		pk.Issuer = issuerid.String()
296
		conf.publicKeys[issuerid][i] = pk
297
	}
298

299
300
301
	return nil
}

302
// parse $schememanager/$issuer/Issues/*/description.xml
303
func (conf *Configuration) parseCredentialsFolder(manager *SchemeManager, path string) error {
304
305
	return iterateSubfolders(path, func(dir string) error {
		cred := &CredentialType{}
306
		exists, err := conf.pathToDescription(manager, dir+"/description.xml", cred)
307
308
309
		if err != nil {
			return err
		}
310
311
312
313
314
		if !exists {
			return nil
		}
		if cred.XMLVersion < 4 {
			return errors.New("Unsupported credential type description")
315
		}
316
		cred.Valid = conf.SchemeManagers[cred.SchemeManagerIdentifier()].Valid
317
318
319
		credid := cred.Identifier()
		conf.CredentialTypes[credid] = cred
		conf.addReverseHash(credid)
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
		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
		}
341
342
343
		if strings.HasSuffix(dir, "/.git") {
			continue
		}
344
345
346
347
348
349
350
351
352
		err = handler(dir)
		if err != nil {
			return err
		}
	}

	return nil
}

353
func (conf *Configuration) pathToDescription(manager *SchemeManager, path string, description interface{}) (bool, error) {
354
355
356
357
	if _, err := os.Stat(path); err != nil {
		return false, nil
	}

358
359
360
361
	bts, found, err := conf.ReadAuthenticatedFile(manager, relativePath(conf.path, path))
	if !found {
		return false, nil
	}
362
363
364
365
	if err != nil {
		return true, err
	}

366
	err = xml.Unmarshal(bts, description)
367
368
369
370
371
372
	if err != nil {
		return true, err
	}

	return true, nil
}
373

374
375
376
377
378
// 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
379
}
380

381
382
383
// CopyFromAssets recursively copies the directory tree from the assets folder
// into the directory of this Configuration.
func (conf *Configuration) CopyFromAssets(parse bool) error {
384
385
386
	if conf.assets == "" {
		return nil
	}
387
	if err := fs.CopyDirectory(conf.assets, conf.path); err != nil {
388
389
390
		return err
	}
	if parse {
391
		return conf.ParseFolder()
392
393
	}
	return nil
394
}
395

396
397
398
399
400
401
402
403
404
405
406
407
func (conf *Configuration) CopyManagerFromAssets(managerID SchemeManagerIdentifier) bool {
	manager := conf.SchemeManagers[managerID]
	if conf.assets == "" {
		return false
	}
	_ = fs.CopyDirectory(
		filepath.Join(conf.assets, manager.ID),
		filepath.Join(conf.path, manager.ID),
	)
	return true
}

408
409
// DownloadSchemeManager downloads and returns a scheme manager description.xml file
// from the specified URL.
410
func (conf *Configuration) DownloadSchemeManager(url string) (*SchemeManager, error) {
411
412
413
414
415
416
417
418
419
	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")]
	}
420
	b, err := NewHTTPTransport(url).GetBytes("description.xml")
421
422
423
	if err != nil {
		return nil, err
	}
424
	manager := &SchemeManager{Status: SchemeManagerStatusUnprocessed, Valid: false}
425
426
427
428
429
430
431
432
	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
433
434
// RemoveSchemeManager removes the specified scheme manager and all associated issuers,
// public keys and credential types from this Configuration.
435
func (conf *Configuration) RemoveSchemeManager(id SchemeManagerIdentifier, fromStorage bool) error {
436
	// Remove everything falling under the manager's responsibility
437
	for credid := range conf.CredentialTypes {
438
		if credid.IssuerIdentifier().SchemeManagerIdentifier() == id {
439
			delete(conf.CredentialTypes, credid)
440
441
		}
	}
442
	for issid := range conf.Issuers {
443
		if issid.SchemeManagerIdentifier() == id {
444
			delete(conf.Issuers, issid)
445
446
		}
	}
447
	for issid := range conf.publicKeys {
448
		if issid.SchemeManagerIdentifier() == id {
449
			delete(conf.publicKeys, issid)
450
451
		}
	}
452
	delete(conf.SchemeManagers, id)
453
454
455
456
457

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

Sietse Ringers's avatar
Sietse Ringers committed
460
461
// AddSchemeManager adds the specified scheme manager to this Configuration,
// provided its signature is valid.
462
func (conf *Configuration) AddSchemeManager(manager *SchemeManager) error {
463
	name := manager.ID
464
	if err := fs.EnsureDirectoryExists(fmt.Sprintf("%s/%s", conf.path, name)); err != nil {
465
466
		return err
	}
467
468
469
470
471
472

	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
	}
473
	if err := t.GetFile("pk.pem", path+"/pk.pem"); err != nil {
474
475
		return err
	}
476
	if err := conf.DownloadSchemeManagerSignature(manager); err != nil {
477
478
		return err
	}
479

480
	return conf.parseSchemeManagerFolder(filepath.Join(conf.path, name), manager)
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
}

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

496
497
498
499
500
501
	defer func() {
		if err != nil {
			_ = conf.restoreManagerSignature(index, sig)
		}
	}()

502
	if err = t.GetFile("index", index); err != nil {
503
		return
504
505
	}
	if err = t.GetFile("index.sig", sig); err != nil {
506
		return
507
	}
Sietse Ringers's avatar
Sietse Ringers committed
508
509
	valid, err := conf.VerifySignature(manager.Identifier())
	if err != nil {
510
		return
Sietse Ringers's avatar
Sietse Ringers committed
511
512
	}
	if !valid {
513
		err = errors.New("Scheme manager signature invalid")
Sietse Ringers's avatar
Sietse Ringers committed
514
	}
515
	return
516
}
517

518
519
520
521
522
523
524
525
526
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
}
527

528
529
func (conf *Configuration) restoreManagerSignature(index, sig string) error {
	if err := fs.Copy(index+".backup", index); err != nil {
530
531
		return err
	}
532
	if err := fs.Copy(sig+".backup", sig); err != nil {
533
534
535
536
537
		return err
	}
	return nil
}

Sietse Ringers's avatar
Sietse Ringers committed
538
539
540
// 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.
541
func (conf *Configuration) Download(set *IrmaIdentifierSet) (*IrmaIdentifierSet, error) {
542
	var contains bool
543
544
545
546
547
548
	var err error
	downloaded := &IrmaIdentifierSet{
		SchemeManagers:  map[SchemeManagerIdentifier]struct{}{},
		Issuers:         map[IssuerIdentifier]struct{}{},
		CredentialTypes: map[CredentialTypeIdentifier]struct{}{},
	}
549
	updatedManagers := make(map[SchemeManagerIdentifier]struct{})
550

551
	for manid := range set.SchemeManagers {
552
		if _, contains = conf.SchemeManagers[manid]; !contains {
553
			return nil, errors.Errorf("Unknown scheme manager: %s", manid)
554
555
556
557
558
		}
	}

	transport := NewHTTPTransport("")
	for issid := range set.Issuers {
559
		if _, contains = conf.Issuers[issid]; !contains {
560
561
562
			manager := issid.SchemeManagerIdentifier()
			url := conf.SchemeManagers[manager].URL + "/" + issid.Name()
			path := fmt.Sprintf("%s/%s/%s", conf.path, manager.String(), issid.Name())
563
564
565
			if err = transport.GetFile(url+"/description.xml", path+"/description.xml"); err != nil {
				return nil, err
			}
566
			if err = transport.GetFile(url+"/logo.png", path+"/logo.png"); err != nil {
567
568
				return nil, err
			}
569
			updatedManagers[manager] = struct{}{}
570
			downloaded.Issuers[issid] = struct{}{}
571
		}
Sietse Ringers's avatar
Sietse Ringers committed
572
573
574
	}
	for issid, list := range set.PublicKeys {
		for _, count := range list {
575
			pk, err := conf.PublicKey(issid, count)
Sietse Ringers's avatar
Sietse Ringers committed
576
577
578
579
580
581
			if err != nil {
				return nil, err
			}
			if pk == nil {
				manager := issid.SchemeManagerIdentifier()
				suffix := fmt.Sprintf("/%s/PublicKeys/%d.xml", issid.Name(), count)
582
				path := fmt.Sprintf("%s/%s/%s", conf.path, manager.String(), suffix)
583
				if err = transport.GetFile(conf.SchemeManagers[manager].URL+suffix, path); err != nil {
584
					return nil, err
585
				}
586
				updatedManagers[manager] = struct{}{}
587
588
589
590
			}
		}
	}
	for credid := range set.CredentialTypes {
591
		if _, contains := conf.CredentialTypes[credid]; !contains {
592
593
			issuer := credid.IssuerIdentifier()
			manager := issuer.SchemeManagerIdentifier()
594
			local := fmt.Sprintf("%s/%s/%s/Issues", conf.path, manager.Name(), issuer.Name())
595
			if err := fs.EnsureDirectoryExists(local); err != nil {
596
				return nil, err
597
			}
598
599
			if err = transport.GetFile(
				fmt.Sprintf("%s/%s/Issues/%s/description.xml", conf.SchemeManagers[manager].URL, issuer.Name(), credid.Name()),
600
				fmt.Sprintf("%s/%s/description.xml", local, credid.Name()),
601
602
603
			); err != nil {
				return nil, err
			}
604
605
606
607
			_ = 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()),
			)
608
			updatedManagers[manager] = struct{}{}
609
			downloaded.CredentialTypes[credid] = struct{}{}
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
	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
643
// FromString populates this index by parsing the specified string.
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
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
}

663
664
665
// 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
666
	if err := fs.AssertPathExists(path); err != nil {
667
		return fmt.Errorf("Missing scheme manager index file; tried %s", path)
668
	}
Sietse Ringers's avatar
Sietse Ringers committed
669
	indexbts, err := ioutil.ReadFile(path)
670
671
672
	if err != nil {
		return err
	}
673
674
	manager.index = make(map[string]ConfigurationFileHash)
	return manager.index.FromString(string(indexbts))
675
676
}

677
func (conf *Configuration) VerifySchemeManager(manager *SchemeManager) error {
678
679
680
681
682
683
684
685
	valid, err := conf.VerifySignature(manager.Identifier())
	if err != nil {
		return err
	}
	if !valid {
		return errors.New("Scheme manager signature was invalid")
	}

686
	for file := range manager.index {
687
688
689
690
691
692
693
		exists, err := fs.PathExists(filepath.Join(conf.path, file))
		if err != nil {
			return err
		}
		if !exists {
			continue
		}
694
		// Don't care about the actual bytes
695
		if _, _, err := conf.ReadAuthenticatedFile(manager, file); err != nil {
696
697
698
699
700
701
702
			return err
		}
	}

	return nil
}

703
704
705
// 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.
706
func (conf *Configuration) ReadAuthenticatedFile(manager *SchemeManager, path string) ([]byte, bool, error) {
707
	signedHash, ok := manager.index[path]
708
	if !ok {
709
		return nil, false, nil
710
711
712
713
	}

	bts, err := ioutil.ReadFile(filepath.Join(conf.path, path))
	if err != nil {
714
		return nil, true, err
715
716
717
718
	}
	computedHash := sha256.Sum256(bts)

	if !bytes.Equal(computedHash[:], signedHash) {
719
		return nil, true, errors.Errorf("Hash of %s does not match scheme manager index", path)
720
	}
721
	return bts, true, nil
722
723
724
725
726
}

// 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).
727
728
729
730
731
732
733
734
735
736
737
738
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")
			}
		}
	}()

739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
	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
776
}
777
778
779
780

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