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

Sietse Ringers's avatar
Sietse Ringers committed
41
42
43
	// Path to the irma_configuration folder that this instance represents
	Path string

44
45
46
	// 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
47

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

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

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

62
63
type SchemeManagerStatus string

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

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

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

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

Sietse Ringers's avatar
Sietse Ringers committed
91
	if err = fs.EnsureDirectoryExists(conf.Path); err != nil {
92
93
		return nil, err
	}
Sietse Ringers's avatar
Sietse Ringers committed
94
	if conf.assets != "" && fs.Empty(conf.Path) {
95
		if err = conf.CopyFromAssets(false); err != nil {
96
97
98
99
			return nil, err
		}
	}

100
101
102
	return
}

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

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

140
141
142
143
144
145
146
147
148
149
150
151
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
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

300
301
302
	return nil
}

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

	return nil
}

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

Sietse Ringers's avatar
Sietse Ringers committed
359
	bts, found, err := conf.ReadAuthenticatedFile(manager, relativePath(conf.Path, path))
360
361
362
	if !found {
		return false, nil
	}
363
364
365
366
	if err != nil {
		return true, err
	}

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

	return true, nil
}
374

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

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

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

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

	if fromStorage {
Sietse Ringers's avatar
Sietse Ringers committed
456
		return os.RemoveAll(fmt.Sprintf("%s/%s", conf.Path, id.String()))
457
458
	}
	return nil
459
460
}

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

	t := NewHTTPTransport(manager.URL)
Sietse Ringers's avatar
Sietse Ringers committed
470
	path := fmt.Sprintf("%s/%s", conf.Path, name)
471
472
473
	if err := t.GetFile("description.xml", path+"/description.xml"); err != nil {
		return err
	}
474
	if err := t.GetFile("pk.pem", path+"/pk.pem"); err != nil {
475
476
		return err
	}
477
	if err := conf.DownloadSchemeManagerSignature(manager); err != nil {
478
479
		return err
	}
480

Sietse Ringers's avatar
Sietse Ringers committed
481
	return conf.parseSchemeManagerFolder(filepath.Join(conf.Path, name), manager)
482
483
484
485
486
487
}

// 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)
Sietse Ringers's avatar
Sietse Ringers committed
488
	path := fmt.Sprintf("%s/%s", conf.Path, manager.ID)
489
490
491
492
493
494
495
496
	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
	}

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

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

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

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

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

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

	transport := NewHTTPTransport("")
	for issid := range set.Issuers {
560
		if _, contains = conf.Issuers[issid]; !contains {
561
562
			manager := issid.SchemeManagerIdentifier()
			url := conf.SchemeManagers[manager].URL + "/" + issid.Name()
Sietse Ringers's avatar
Sietse Ringers committed
563
			path := fmt.Sprintf("%s/%s/%s", conf.Path, manager.String(), issid.Name())
564
565
566
			if err = transport.GetFile(url+"/description.xml", path+"/description.xml"); err != nil {
				return nil, err
			}
567
			if err = transport.GetFile(url+"/logo.png", path+"/logo.png"); err != nil {
568
569
				return nil, err
			}
570
			updatedManagers[manager] = struct{}{}
571
			downloaded.Issuers[issid] = struct{}{}
572
		}
Sietse Ringers's avatar
Sietse Ringers committed
573
574
575
	}
	for issid, list := range set.PublicKeys {
		for _, count := range list {
576
			pk, err := conf.PublicKey(issid, count)
Sietse Ringers's avatar
Sietse Ringers committed
577
578
579
580
581
582
			if err != nil {
				return nil, err
			}
			if pk == nil {
				manager := issid.SchemeManagerIdentifier()
				suffix := fmt.Sprintf("/%s/PublicKeys/%d.xml", issid.Name(), count)
Sietse Ringers's avatar
Sietse Ringers committed
583
				path := fmt.Sprintf("%s/%s/%s", conf.Path, manager.String(), suffix)
584
				if err = transport.GetFile(conf.SchemeManagers[manager].URL+suffix, path); err != nil {
585
					return nil, err
586
				}
587
				updatedManagers[manager] = struct{}{}
588
589
590
591
			}
		}
	}
	for credid := range set.CredentialTypes {
592
		if _, contains := conf.CredentialTypes[credid]; !contains {
593
594
			issuer := credid.IssuerIdentifier()
			manager := issuer.SchemeManagerIdentifier()
Sietse Ringers's avatar
Sietse Ringers committed
595
			local := fmt.Sprintf("%s/%s/%s/Issues", conf.Path, manager.Name(), issuer.Name())
596
			if err := fs.EnsureDirectoryExists(local); err != nil {
597
				return nil, err
598
			}
599
600
			if err = transport.GetFile(
				fmt.Sprintf("%s/%s/Issues/%s/description.xml", conf.SchemeManagers[manager].URL, issuer.Name(), credid.Name()),
601
				fmt.Sprintf("%s/%s/description.xml", local, credid.Name()),
602
603
604
			); err != nil {
				return nil, err
			}
605
606
607
608
			_ = 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()),
			)
609
			updatedManagers[manager] = struct{}{}
610
			downloaded.CredentialTypes[credid] = struct{}{}
611
612
613
		}
	}

614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
	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
644
// FromString populates this index by parsing the specified string.
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
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
}

664
665
// parseIndex parses the index file of the specified manager.
func (conf *Configuration) parseIndex(name string, manager *SchemeManager) error {
Sietse Ringers's avatar
Sietse Ringers committed
666
	path := filepath.Join(conf.Path, name, "index")
Sietse Ringers's avatar
Sietse Ringers committed
667
	if err := fs.AssertPathExists(path); err != nil {
668
		return fmt.Errorf("Missing scheme manager index file; tried %s", path)
669
	}
Sietse Ringers's avatar
Sietse Ringers committed
670
	indexbts, err := ioutil.ReadFile(path)
671
672
673
	if err != nil {
		return err
	}
674
675
	manager.index = make(map[string]ConfigurationFileHash)
	return manager.index.FromString(string(indexbts))
676
677
}

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

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

	return nil
}

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

Sietse Ringers's avatar
Sietse Ringers committed
713
	bts, err := ioutil.ReadFile(filepath.Join(conf.Path, path))
714
	if err != nil {
715
		return nil, true, err
716
717
718
719
	}
	computedHash := sha256.Sum256(bts)

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

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

Sietse Ringers's avatar
Sietse Ringers committed
740
	dir := filepath.Join(conf.Path, id.String())
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
776
	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
777
}
778
779
780
781

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