ソースを参照

fix #530, #721

tags/v2.0.0-rc1
Shivaram Lingamneni 4年前
コミット
f920d3b79f
16個のファイルの変更447行の追加298行の削除
  1. 2
    8
      gencapdefs.py
  2. 187
    44
      irc/accounts.go
  3. 3
    8
      irc/caps/defs.go
  4. 1
    0
      irc/chanserv.go
  5. 2
    1
      irc/client.go
  6. 0
    5
      irc/commands.go
  7. 6
    1
      irc/config.go
  8. 57
    1
      irc/database.go
  9. 4
    0
      irc/errors.go
  10. 6
    2
      irc/gateways.go
  11. 2
    190
      irc/handlers.go
  12. 1
    1
      irc/legacy.go
  13. 145
    14
      irc/nickserv.go
  14. 11
    9
      irc/utils/crypto.go
  15. 17
    11
      irc/utils/crypto_test.go
  16. 3
    3
      oragono.yaml

+ 2
- 8
gencapdefs.py ファイルの表示

15
 CapDef = namedtuple("CapDef", ['identifier', 'name', 'url', 'standard'])
15
 CapDef = namedtuple("CapDef", ['identifier', 'name', 'url', 'standard'])
16
 
16
 
17
 CAPDEFS = [
17
 CAPDEFS = [
18
-    CapDef(
19
-        identifier="Acc",
20
-        name="draft/acc",
21
-        url="https://github.com/ircv3/ircv3-specifications/pull/276",
22
-        standard="proposed IRCv3",
23
-    ),
24
     CapDef(
18
     CapDef(
25
         identifier="AccountNotify",
19
         identifier="AccountNotify",
26
         name="account-notify",
20
         name="account-notify",
163
         identifier="EventPlayback",
157
         identifier="EventPlayback",
164
         name="draft/event-playback",
158
         name="draft/event-playback",
165
         url="https://github.com/ircv3/ircv3-specifications/pull/362",
159
         url="https://github.com/ircv3/ircv3-specifications/pull/362",
166
-        standard="Proposed IRCv3",
160
+        standard="proposed IRCv3",
167
     ),
161
     ),
168
     CapDef(
162
     CapDef(
169
         identifier="ZNCPlayback",
163
         identifier="ZNCPlayback",
181
         identifier="Multiline",
175
         identifier="Multiline",
182
         name="draft/multiline",
176
         name="draft/multiline",
183
         url="https://github.com/ircv3/ircv3-specifications/pull/398",
177
         url="https://github.com/ircv3/ircv3-specifications/pull/398",
184
-        standard="Proposed IRCv3",
178
+        standard="proposed IRCv3",
185
     ),
179
     ),
186
 ]
180
 ]
187
 
181
 

+ 187
- 44
irc/accounts.go ファイルの表示

36
 
36
 
37
 	keyVHostQueueAcctToId = "vhostQueue %s"
37
 	keyVHostQueueAcctToId = "vhostQueue %s"
38
 	vhostRequestIdx       = "vhostQueue"
38
 	vhostRequestIdx       = "vhostQueue"
39
+
40
+	maxCertfpsPerAccount = 5
39
 )
41
 )
40
 
42
 
41
 // everything about accounts is persistent; therefore, the database is the authoritative
43
 // everything about accounts is persistent; therefore, the database is the authoritative
327
 	verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
329
 	verificationCodeKey := fmt.Sprintf(keyAccountVerificationCode, casefoldedAccount)
328
 	certFPKey := fmt.Sprintf(keyCertToAccount, certfp)
330
 	certFPKey := fmt.Sprintf(keyCertToAccount, certfp)
329
 
331
 
330
-	credStr, err := am.serializeCredentials(passphrase, certfp)
332
+	var creds AccountCredentials
333
+	creds.Version = 1
334
+	err = creds.SetPassphrase(passphrase, am.server.Config().Accounts.Registration.BcryptCost)
335
+	if err != nil {
336
+		return err
337
+	}
338
+	creds.AddCertfp(certfp)
339
+	credStr, err := creds.Serialize()
331
 	if err != nil {
340
 	if err != nil {
332
 		return err
341
 		return err
333
 	}
342
 	}
411
 	return nil
420
 	return nil
412
 }
421
 }
413
 
422
 
414
-// helper to assemble the serialized JSON for an account's credentials
415
-func (am *AccountManager) serializeCredentials(passphrase string, certfp string) (result string, err error) {
423
+// changes the password for an account
424
+func (am *AccountManager) setPassword(account string, password string, hasPrivs bool) (err error) {
425
+	cfAccount, err := CasefoldName(account)
426
+	if err != nil {
427
+		return errAccountDoesNotExist
428
+	}
429
+
430
+	credKey := fmt.Sprintf(keyAccountCredentials, cfAccount)
431
+	var credStr string
432
+	am.server.store.View(func(tx *buntdb.Tx) error {
433
+		// no need to check verification status here or below;
434
+		// you either need to be auth'ed to the account or be an oper to do this
435
+		credStr, err = tx.Get(credKey)
436
+		return nil
437
+	})
438
+
439
+	if err != nil {
440
+		return errAccountDoesNotExist
441
+	}
442
+
416
 	var creds AccountCredentials
443
 	var creds AccountCredentials
417
-	creds.Version = 1
418
-	// we need at least one of passphrase and certfp:
419
-	if passphrase == "" && certfp == "" {
420
-		return "", errAccountBadPassphrase
421
-	}
422
-	// but if we have one, it's fine if the other is missing, it just means no
423
-	// credential of that type will be accepted.
424
-	creds.Certificate = certfp
425
-	if passphrase != "" {
426
-		if validatePassphrase(passphrase) != nil {
427
-			return "", errAccountBadPassphrase
428
-		}
429
-		bcryptCost := int(am.server.Config().Accounts.Registration.BcryptCost)
430
-		creds.PassphraseHash, err = passwd.GenerateFromPassword([]byte(passphrase), bcryptCost)
431
-		if err != nil {
432
-			am.server.logger.Error("internal", "could not hash password", err.Error())
433
-			return "", errAccountCreation
434
-		}
444
+	err = json.Unmarshal([]byte(credStr), &creds)
445
+	if err != nil {
446
+		return err
435
 	}
447
 	}
436
 
448
 
437
-	credText, err := json.Marshal(creds)
449
+	err = creds.SetPassphrase(password, am.server.Config().Accounts.Registration.BcryptCost)
438
 	if err != nil {
450
 	if err != nil {
439
-		am.server.logger.Error("internal", "could not marshal credentials", err.Error())
440
-		return "", errAccountCreation
451
+		return err
441
 	}
452
 	}
442
-	return string(credText), nil
453
+
454
+	if creds.Empty() && !hasPrivs {
455
+		return errEmptyCredentials
456
+	}
457
+
458
+	newCredStr, err := creds.Serialize()
459
+	if err != nil {
460
+		return err
461
+	}
462
+
463
+	err = am.server.store.Update(func(tx *buntdb.Tx) error {
464
+		curCredStr, err := tx.Get(credKey)
465
+		if credStr != curCredStr {
466
+			return errCASFailed
467
+		}
468
+		_, _, err = tx.Set(credKey, newCredStr, nil)
469
+		return err
470
+	})
471
+
472
+	return err
443
 }
473
 }
444
 
474
 
445
-// changes the password for an account
446
-func (am *AccountManager) setPassword(account string, password string) (err error) {
447
-	casefoldedAccount, err := CasefoldName(account)
475
+func (am *AccountManager) addRemoveCertfp(account, certfp string, add bool, hasPrivs bool) (err error) {
476
+	certfp, err = utils.NormalizeCertfp(certfp)
477
+	if err != nil {
478
+		return err
479
+	}
480
+
481
+	cfAccount, err := CasefoldName(account)
482
+	if err != nil {
483
+		return errAccountDoesNotExist
484
+	}
485
+
486
+	credKey := fmt.Sprintf(keyAccountCredentials, cfAccount)
487
+	var credStr string
488
+	am.server.store.View(func(tx *buntdb.Tx) error {
489
+		credStr, err = tx.Get(credKey)
490
+		return nil
491
+	})
492
+
493
+	if err != nil {
494
+		return errAccountDoesNotExist
495
+	}
496
+
497
+	var creds AccountCredentials
498
+	err = json.Unmarshal([]byte(credStr), &creds)
448
 	if err != nil {
499
 	if err != nil {
449
 		return err
500
 		return err
450
 	}
501
 	}
451
-	act, err := am.LoadAccount(casefoldedAccount)
502
+
503
+	if add {
504
+		err = creds.AddCertfp(certfp)
505
+	} else {
506
+		err = creds.RemoveCertfp(certfp)
507
+	}
452
 	if err != nil {
508
 	if err != nil {
453
 		return err
509
 		return err
454
 	}
510
 	}
455
 
511
 
456
-	credStr, err := am.serializeCredentials(password, act.Credentials.Certificate)
512
+	if creds.Empty() && !hasPrivs {
513
+		return errEmptyCredentials
514
+	}
515
+
516
+	newCredStr, err := creds.Serialize()
457
 	if err != nil {
517
 	if err != nil {
458
 		return err
518
 		return err
459
 	}
519
 	}
460
 
520
 
461
-	credentialsKey := fmt.Sprintf(keyAccountCredentials, casefoldedAccount)
462
-	return am.server.store.Update(func(tx *buntdb.Tx) error {
463
-		_, _, err := tx.Set(credentialsKey, credStr, nil)
521
+	certfpKey := fmt.Sprintf(keyCertToAccount, certfp)
522
+	err = am.server.store.Update(func(tx *buntdb.Tx) error {
523
+		curCredStr, err := tx.Get(credKey)
524
+		if credStr != curCredStr {
525
+			return errCASFailed
526
+		}
527
+		if add {
528
+			_, err = tx.Get(certfpKey)
529
+			if err != buntdb.ErrNotFound {
530
+				return errCertfpAlreadyExists
531
+			}
532
+			tx.Set(certfpKey, cfAccount, nil)
533
+		} else {
534
+			tx.Delete(certfpKey)
535
+		}
536
+		_, _, err = tx.Set(credKey, newCredStr, nil)
464
 		return err
537
 		return err
465
 	})
538
 	})
539
+
540
+	return err
466
 }
541
 }
467
 
542
 
468
 func (am *AccountManager) dispatchCallback(client *Client, casefoldedAccount string, callbackNamespace string, callbackValue string) (string, error) {
543
 func (am *AccountManager) dispatchCallback(client *Client, casefoldedAccount string, callbackNamespace string, callbackValue string) (string, error) {
574
 			// XXX we shouldn't do (de)serialization inside the txn,
649
 			// XXX we shouldn't do (de)serialization inside the txn,
575
 			// but this is like 2 usec on my system
650
 			// but this is like 2 usec on my system
576
 			json.Unmarshal([]byte(raw.Credentials), &creds)
651
 			json.Unmarshal([]byte(raw.Credentials), &creds)
577
-			if creds.Certificate != "" {
578
-				certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
652
+			for _, cert := range creds.Certfps {
653
+				certFPKey := fmt.Sprintf(keyCertToAccount, cert)
579
 				tx.Set(certFPKey, casefoldedAccount, nil)
654
 				tx.Set(certFPKey, casefoldedAccount, nil)
580
 			}
655
 			}
581
 
656
 
906
 
981
 
907
 	if err == nil {
982
 	if err == nil {
908
 		var creds AccountCredentials
983
 		var creds AccountCredentials
909
-		if err = json.Unmarshal([]byte(credText), &creds); err == nil && creds.Certificate != "" {
910
-			certFPKey := fmt.Sprintf(keyCertToAccount, creds.Certificate)
911
-			am.server.store.Update(func(tx *buntdb.Tx) error {
912
-				if account, err := tx.Get(certFPKey); err == nil && account == casefoldedAccount {
913
-					tx.Delete(certFPKey)
914
-				}
915
-				return nil
916
-			})
984
+		if err = json.Unmarshal([]byte(credText), &creds); err == nil {
985
+			for _, cert := range creds.Certfps {
986
+				certFPKey := fmt.Sprintf(keyCertToAccount, cert)
987
+				am.server.store.Update(func(tx *buntdb.Tx) error {
988
+					if account, err := tx.Get(certFPKey); err == nil && account == casefoldedAccount {
989
+						tx.Delete(certFPKey)
990
+					}
991
+					return nil
992
+				})
993
+			}
917
 		}
994
 		}
918
 	}
995
 	}
919
 
996
 
1326
 	Version        uint
1403
 	Version        uint
1327
 	PassphraseSalt []byte // legacy field, not used by v1 and later
1404
 	PassphraseSalt []byte // legacy field, not used by v1 and later
1328
 	PassphraseHash []byte
1405
 	PassphraseHash []byte
1329
-	Certificate    string // fingerprint
1406
+	Certfps        []string
1407
+}
1408
+
1409
+func (ac *AccountCredentials) Empty() bool {
1410
+	return len(ac.PassphraseHash) == 0 && len(ac.Certfps) == 0
1411
+}
1412
+
1413
+// helper to assemble the serialized JSON for an account's credentials
1414
+func (ac *AccountCredentials) Serialize() (result string, err error) {
1415
+	ac.Version = 1
1416
+	credText, err := json.Marshal(*ac)
1417
+	if err != nil {
1418
+		return "", err
1419
+	}
1420
+	return string(credText), nil
1421
+}
1422
+
1423
+func (ac *AccountCredentials) SetPassphrase(passphrase string, bcryptCost uint) (err error) {
1424
+	if passphrase == "" {
1425
+		ac.PassphraseHash = nil
1426
+		return nil
1427
+	}
1428
+
1429
+	if validatePassphrase(passphrase) != nil {
1430
+		return errAccountBadPassphrase
1431
+	}
1432
+
1433
+	ac.PassphraseHash, err = passwd.GenerateFromPassword([]byte(passphrase), int(bcryptCost))
1434
+	if err != nil {
1435
+		return errAccountBadPassphrase
1436
+	}
1437
+
1438
+	return nil
1439
+}
1440
+
1441
+func (ac *AccountCredentials) AddCertfp(certfp string) (err error) {
1442
+	for _, current := range ac.Certfps {
1443
+		if certfp == current {
1444
+			return errNoop
1445
+		}
1446
+	}
1447
+
1448
+	if maxCertfpsPerAccount <= len(ac.Certfps) {
1449
+		return errLimitExceeded
1450
+	}
1451
+
1452
+	ac.Certfps = append(ac.Certfps, certfp)
1453
+	return nil
1454
+}
1455
+
1456
+func (ac *AccountCredentials) RemoveCertfp(certfp string) (err error) {
1457
+	found := false
1458
+	newList := make([]string, 0, len(ac.Certfps))
1459
+	for _, current := range ac.Certfps {
1460
+		if current == certfp {
1461
+			found = true
1462
+		} else {
1463
+			newList = append(newList, current)
1464
+		}
1465
+	}
1466
+	if !found {
1467
+		// this is important because it prevents you from deleting someone else's
1468
+		// fingerprint record
1469
+		return errNoop
1470
+	}
1471
+	ac.Certfps = newList
1472
+	return nil
1330
 }
1473
 }
1331
 
1474
 
1332
 type BouncerAllowedSetting int
1475
 type BouncerAllowedSetting int

+ 3
- 8
irc/caps/defs.go ファイルの表示

7
 
7
 
8
 const (
8
 const (
9
 	// number of recognized capabilities:
9
 	// number of recognized capabilities:
10
-	numCapabs = 28
10
+	numCapabs = 27
11
 	// length of the uint64 array that represents the bitset:
11
 	// length of the uint64 array that represents the bitset:
12
 	bitsetLen = 1
12
 	bitsetLen = 1
13
 )
13
 )
37
 	// https://ircv3.net/specs/extensions/chghost-3.2.html
37
 	// https://ircv3.net/specs/extensions/chghost-3.2.html
38
 	ChgHost Capability = iota
38
 	ChgHost Capability = iota
39
 
39
 
40
-	// Acc is the proposed IRCv3 capability named "draft/acc":
41
-	// https://github.com/ircv3/ircv3-specifications/pull/276
42
-	Acc Capability = iota
43
-
44
-	// EventPlayback is the Proposed IRCv3 capability named "draft/event-playback":
40
+	// EventPlayback is the proposed IRCv3 capability named "draft/event-playback":
45
 	// https://github.com/ircv3/ircv3-specifications/pull/362
41
 	// https://github.com/ircv3/ircv3-specifications/pull/362
46
 	EventPlayback Capability = iota
42
 	EventPlayback Capability = iota
47
 
43
 
53
 	// https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
49
 	// https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
54
 	Languages Capability = iota
50
 	Languages Capability = iota
55
 
51
 
56
-	// Multiline is the Proposed IRCv3 capability named "draft/multiline":
52
+	// Multiline is the proposed IRCv3 capability named "draft/multiline":
57
 	// https://github.com/ircv3/ircv3-specifications/pull/398
53
 	// https://github.com/ircv3/ircv3-specifications/pull/398
58
 	Multiline Capability = iota
54
 	Multiline Capability = iota
59
 
55
 
135
 		"batch",
131
 		"batch",
136
 		"cap-notify",
132
 		"cap-notify",
137
 		"chghost",
133
 		"chghost",
138
-		"draft/acc",
139
 		"draft/event-playback",
134
 		"draft/event-playback",
140
 		"draft/labeled-response-0.2",
135
 		"draft/labeled-response-0.2",
141
 		"draft/languages",
136
 		"draft/languages",

+ 1
- 0
irc/chanserv.go ファイルの表示

134
 
134
 
135
 INFO displays info about a registered channel.`,
135
 INFO displays info about a registered channel.`,
136
 			helpShort: `$bINFO$b displays info about a registered channel.`,
136
 			helpShort: `$bINFO$b displays info about a registered channel.`,
137
+			enabled:   chanregEnabled,
137
 			minParams: 1,
138
 			minParams: 1,
138
 		},
139
 		},
139
 	}
140
 	}

+ 2
- 1
irc/client.go ファイルの表示

1419
 		return
1419
 		return
1420
 	}
1420
 	}
1421
 	for _, oper := range client.server.Config().operators {
1421
 	for _, oper := range client.server.Config().operators {
1422
-		if oper.Auto && oper.Pass == nil && utils.CertfpsMatch(oper.Fingerprint, client.certfp) {
1422
+		if oper.Auto && oper.Pass == nil && oper.Fingerprint != "" && oper.Fingerprint == client.certfp {
1423
 			rb := NewResponseBuffer(session)
1423
 			rb := NewResponseBuffer(session)
1424
 			applyOper(client, oper, rb)
1424
 			applyOper(client, oper, rb)
1425
 			rb.Send(true)
1425
 			rb.Send(true)
1426
+			return
1426
 		}
1427
 		}
1427
 	}
1428
 	}
1428
 }
1429
 }

+ 0
- 5
irc/commands.go ファイルの表示

80
 
80
 
81
 func init() {
81
 func init() {
82
 	Commands = map[string]Command{
82
 	Commands = map[string]Command{
83
-		"ACC": {
84
-			handler:      accHandler,
85
-			usablePreReg: true,
86
-			minParams:    1,
87
-		},
88
 		"AMBIANCE": {
83
 		"AMBIANCE": {
89
 			handler:   sceneHandler,
84
 			handler:   sceneHandler,
90
 			minParams: 2,
85
 			minParams: 2,

+ 6
- 1
irc/config.go ファイルの表示

511
 				return nil, fmt.Errorf("Oper %s has an invalid password hash: %s", oper.Name, err.Error())
511
 				return nil, fmt.Errorf("Oper %s has an invalid password hash: %s", oper.Name, err.Error())
512
 			}
512
 			}
513
 		}
513
 		}
514
-		oper.Fingerprint = opConf.Fingerprint
514
+		if opConf.Fingerprint != "" {
515
+			oper.Fingerprint, err = utils.NormalizeCertfp(opConf.Fingerprint)
516
+			if err != nil {
517
+				return nil, fmt.Errorf("Oper %s has an invalid fingerprint: %s", oper.Name, err.Error())
518
+			}
519
+		}
515
 		oper.Auto = opConf.Auto
520
 		oper.Auto = opConf.Auto
516
 
521
 
517
 		if oper.Pass == nil && oper.Fingerprint == "" {
522
 		if oper.Pass == nil && oper.Fingerprint == "" {

+ 57
- 1
irc/database.go ファイルの表示

22
 	// 'version' of the database schema
22
 	// 'version' of the database schema
23
 	keySchemaVersion = "db.version"
23
 	keySchemaVersion = "db.version"
24
 	// latest schema of the db
24
 	// latest schema of the db
25
-	latestDbSchema = "8"
25
+	latestDbSchema = "9"
26
 )
26
 )
27
 
27
 
28
 type SchemaChanger func(*Config, *buntdb.Tx) error
28
 type SchemaChanger func(*Config, *buntdb.Tx) error
553
 	return nil
553
 	return nil
554
 }
554
 }
555
 
555
 
556
+type accountCredsLegacyV8 struct {
557
+	Version        uint
558
+	PassphraseSalt []byte // legacy field, not used by v1 and later
559
+	PassphraseHash []byte
560
+	Certificate    string
561
+}
562
+
563
+type accountCredsLegacyV9 struct {
564
+	Version        uint
565
+	PassphraseSalt []byte // legacy field, not used by v1 and later
566
+	PassphraseHash []byte
567
+	Certfps        []string
568
+}
569
+
570
+// #530: support multiple client certificate fingerprints
571
+func schemaChangeV8ToV9(config *Config, tx *buntdb.Tx) error {
572
+	prefix := "account.credentials "
573
+	var accounts, blobs []string
574
+	tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
575
+		var legacy accountCredsLegacyV8
576
+		var current accountCredsLegacyV9
577
+		if !strings.HasPrefix(key, prefix) {
578
+			return false
579
+		}
580
+		account := strings.TrimPrefix(key, prefix)
581
+		err := json.Unmarshal([]byte(value), &legacy)
582
+		if err != nil {
583
+			log.Printf("corrupt record for %s: %v\n", account, err)
584
+			return true
585
+		}
586
+		current.Version = legacy.Version
587
+		current.PassphraseSalt = legacy.PassphraseSalt // ugh can't get rid of this
588
+		current.PassphraseHash = legacy.PassphraseHash
589
+		if legacy.Certificate != "" {
590
+			current.Certfps = []string{legacy.Certificate}
591
+		}
592
+		blob, err := json.Marshal(current)
593
+		if err != nil {
594
+			log.Printf("could not marshal record for %s: %v\n", account, err)
595
+			return true
596
+		}
597
+		accounts = append(accounts, account)
598
+		blobs = append(blobs, string(blob))
599
+		return true
600
+	})
601
+	for i, account := range accounts {
602
+		tx.Set(prefix+account, blobs[i], nil)
603
+	}
604
+	return nil
605
+}
606
+
556
 func init() {
607
 func init() {
557
 	allChanges := []SchemaChange{
608
 	allChanges := []SchemaChange{
558
 		{
609
 		{
590
 			TargetVersion:  "8",
641
 			TargetVersion:  "8",
591
 			Changer:        schemaChangeV7ToV8,
642
 			Changer:        schemaChangeV7ToV8,
592
 		},
643
 		},
644
+		{
645
+			InitialVersion: "8",
646
+			TargetVersion:  "9",
647
+			Changer:        schemaChangeV8ToV9,
648
+		},
593
 	}
649
 	}
594
 
650
 
595
 	// build the index
651
 	// build the index

+ 4
- 0
irc/errors.go ファイルの表示

50
 	errBanned                         = errors.New("IP or nickmask banned")
50
 	errBanned                         = errors.New("IP or nickmask banned")
51
 	errInvalidParams                  = utils.ErrInvalidParams
51
 	errInvalidParams                  = utils.ErrInvalidParams
52
 	errNoVhost                        = errors.New(`You do not have an approved vhost`)
52
 	errNoVhost                        = errors.New(`You do not have an approved vhost`)
53
+	errLimitExceeded                  = errors.New("Limit exceeded")
54
+	errNoop                           = errors.New("Action was a no-op")
55
+	errCASFailed                      = errors.New("Compare-and-swap update of database value failed")
56
+	errEmptyCredentials               = errors.New("No more credentials are approved")
53
 )
57
 )
54
 
58
 
55
 // Socket Errors
59
 // Socket Errors

+ 6
- 2
irc/gateways.go ファイルの表示

39
 // Populate fills out our password or fingerprint.
39
 // Populate fills out our password or fingerprint.
40
 func (wc *webircConfig) Populate() (err error) {
40
 func (wc *webircConfig) Populate() (err error) {
41
 	if wc.Fingerprint == "" && wc.PasswordString == "" {
41
 	if wc.Fingerprint == "" && wc.PasswordString == "" {
42
-		return ErrNoFingerprintOrPassword
42
+		err = ErrNoFingerprintOrPassword
43
 	}
43
 	}
44
 
44
 
45
-	if wc.PasswordString != "" {
45
+	if err == nil && wc.PasswordString != "" {
46
 		wc.Password, err = decodeLegacyPasswordHash(wc.PasswordString)
46
 		wc.Password, err = decodeLegacyPasswordHash(wc.PasswordString)
47
 	}
47
 	}
48
 
48
 
49
+	if err == nil && wc.Fingerprint != "" {
50
+		wc.Fingerprint, err = utils.NormalizeCertfp(wc.Fingerprint)
51
+	}
52
+
49
 	if err == nil {
53
 	if err == nil {
50
 		wc.allowedNets, err = utils.ParseNetList(wc.Hosts)
54
 		wc.allowedNets, err = utils.ParseNetList(wc.Hosts)
51
 	}
55
 	}

+ 2
- 190
irc/handlers.go ファイルの表示

31
 	"golang.org/x/crypto/bcrypt"
31
 	"golang.org/x/crypto/bcrypt"
32
 )
32
 )
33
 
33
 
34
-// ACC [LS|REGISTER|VERIFY] ...
35
-func accHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
36
-	subcommand := strings.ToLower(msg.Params[0])
37
-
38
-	if subcommand == "ls" {
39
-		config := server.Config().Accounts
40
-
41
-		rb.Add(nil, server.name, "ACC", "LS", "SUBCOMMANDS", "LS REGISTER VERIFY")
42
-
43
-		// this list is sorted by the config loader, yay
44
-		rb.Add(nil, server.name, "ACC", "LS", "CALLBACKS", strings.Join(config.Registration.EnabledCallbacks, " "))
45
-
46
-		rb.Add(nil, server.name, "ACC", "LS", "CREDTYPES", "passphrase certfp")
47
-
48
-		flags := []string{"nospaces"}
49
-		if config.NickReservation.Enabled {
50
-			flags = append(flags, "regnick")
51
-		}
52
-		sort.Strings(flags)
53
-		rb.Add(nil, server.name, "ACC", "LS", "FLAGS", strings.Join(flags, " "))
54
-		return false
55
-	}
56
-
57
-	// disallow account stuff before connection registration has completed, for now
58
-	if !client.Registered() {
59
-		client.Send(nil, server.name, ERR_NOTREGISTERED, "*", client.t("You need to register before you can use that command"))
60
-		return false
61
-	}
62
-
63
-	// make sure reg is enabled
64
-	if !server.AccountConfig().Registration.Enabled {
65
-		rb.Add(nil, server.name, "FAIL", "ACC", "REG_UNAVAILABLE", client.t("Account registration is disabled"))
66
-		return false
67
-	}
68
-
69
-	if subcommand == "register" {
70
-		return accRegisterHandler(server, client, msg, rb)
71
-	} else if subcommand == "verify" {
72
-		return accVerifyHandler(server, client, msg, rb)
73
-	} else {
74
-		rb.Add(nil, server.name, ERR_UNKNOWNERROR, client.nick, "ACC", msg.Params[0], client.t("Unknown subcommand"))
75
-	}
76
-
77
-	return false
78
-}
79
-
80
 // helper function to parse ACC callbacks, e.g., mailto:person@example.com, tel:16505551234
34
 // helper function to parse ACC callbacks, e.g., mailto:person@example.com, tel:16505551234
81
 func parseCallback(spec string, config *AccountConfig) (callbackNamespace string, callbackValue string) {
35
 func parseCallback(spec string, config *AccountConfig) (callbackNamespace string, callbackValue string) {
82
 	callback := strings.ToLower(spec)
36
 	callback := strings.ToLower(spec)
103
 	return
57
 	return
104
 }
58
 }
105
 
59
 
106
-// ACC REGISTER <accountname> [callback_namespace:]<callback> [cred_type] :<credential>
107
-func accRegisterHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
108
-	nick := client.Nick()
109
-
110
-	if len(msg.Params) < 4 {
111
-		rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, nick, msg.Command, client.t("Not enough parameters"))
112
-		return false
113
-	}
114
-
115
-	account := msg.Params[1]
116
-
117
-	// check for account name of *
118
-	if account == "*" {
119
-		account = nick
120
-	} else {
121
-		if server.Config().Accounts.NickReservation.Enabled {
122
-			rb.Add(nil, server.name, "FAIL", "ACC", "REG_MUST_USE_REGNICK", account, client.t("Must register with current nickname instead of separate account name"))
123
-			return false
124
-		}
125
-	}
126
-
127
-	// clients can't reg new accounts if they're already logged in
128
-	if client.LoggedIntoAccount() {
129
-		rb.Add(nil, server.name, "FAIL", "ACC", "REG_UNSPECIFIED_ERROR", account, client.t("You're already logged into an account"))
130
-		return false
131
-	}
132
-
133
-	// sanitise account name
134
-	casefoldedAccount, err := CasefoldName(account)
135
-	if err != nil {
136
-		rb.Add(nil, server.name, "FAIL", "ACC", "REG_INVALID_ACCOUNT_NAME", account, client.t("Account name is not valid"))
137
-		return false
138
-	}
139
-
140
-	callbackSpec := msg.Params[2]
141
-	callbackNamespace, callbackValue := parseCallback(callbackSpec, server.AccountConfig())
142
-
143
-	if callbackNamespace == "" {
144
-		rb.Add(nil, server.name, "FAIL", "ACC", "REG_INVALID_CALLBACK", account, callbackSpec, client.t("Cannot send verification code there"))
145
-		return false
146
-	}
147
-
148
-	// get credential type/value
149
-	var credentialType, credentialValue string
150
-
151
-	if len(msg.Params) > 4 {
152
-		credentialType = strings.ToLower(msg.Params[3])
153
-		credentialValue = msg.Params[4]
154
-	} else {
155
-		// exactly 4 params
156
-		credentialType = "passphrase" // default from the spec
157
-		credentialValue = msg.Params[3]
158
-	}
159
-
160
-	// ensure the credential type is valid
161
-	var credentialValid bool
162
-	for _, name := range server.AccountConfig().Registration.EnabledCredentialTypes {
163
-		if credentialType == name {
164
-			credentialValid = true
165
-		}
166
-	}
167
-	if credentialType == "certfp" && client.certfp == "" {
168
-		rb.Add(nil, server.name, "FAIL", "ACC", "REG_INVALID_CREDENTIAL", account, client.t("You must connect with a TLS client certificate to use certfp"))
169
-		return false
170
-	}
171
-
172
-	if !credentialValid {
173
-		rb.Add(nil, server.name, "FAIL", "ACC", "REG_INVALID_CRED_TYPE", account, credentialType, client.t("Credential type is not supported"))
174
-		return false
175
-	}
176
-
177
-	var passphrase, certfp string
178
-	if credentialType == "certfp" {
179
-		certfp = client.certfp
180
-	} else if credentialType == "passphrase" {
181
-		passphrase = credentialValue
182
-	}
183
-
184
-	throttled, remainingTime := client.loginThrottle.Touch()
185
-	if throttled {
186
-		rb.Add(nil, server.name, "FAIL", "ACC", "REG_UNSPECIFIED_ERROR", account, fmt.Sprintf(client.t("Please wait at least %v and try again"), remainingTime))
187
-		return false
188
-	}
189
-
190
-	err = server.accounts.Register(client, account, callbackNamespace, callbackValue, passphrase, certfp)
191
-	if err != nil {
192
-		msg, code := registrationErrorToMessageAndCode(err)
193
-		rb.Add(nil, server.name, "FAIL", "ACC", code, account, client.t(msg))
194
-		return false
195
-	}
196
-
197
-	// automatically complete registration
198
-	if callbackNamespace == "*" {
199
-		err := server.accounts.Verify(client, casefoldedAccount, "")
200
-		if err != nil {
201
-			return false
202
-		}
203
-		sendSuccessfulRegResponse(client, rb, false)
204
-	} else {
205
-		messageTemplate := client.t("Account created, pending verification; verification code has been sent to %s")
206
-		message := fmt.Sprintf(messageTemplate, fmt.Sprintf("%s:%s", callbackNamespace, callbackValue))
207
-		rb.Add(nil, server.name, RPL_REG_VERIFICATION_REQUIRED, nick, casefoldedAccount, message)
208
-	}
209
-
210
-	return false
211
-}
212
-
213
 func registrationErrorToMessageAndCode(err error) (message, code string) {
60
 func registrationErrorToMessageAndCode(err error) (message, code string) {
214
 	// default responses: let's be risk-averse about displaying internal errors
61
 	// default responses: let's be risk-averse about displaying internal errors
215
 	// to the clients, especially for something as sensitive as accounts
62
 	// to the clients, especially for something as sensitive as accounts
263
 	client.server.logger.Info("accounts", "client", details.nick, "logged into account", details.accountName)
110
 	client.server.logger.Info("accounts", "client", details.nick, "logged into account", details.accountName)
264
 }
111
 }
265
 
112
 
266
-// ACC VERIFY <accountname> <auth_code>
267
-func accVerifyHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
268
-	account := strings.TrimSpace(msg.Params[1])
269
-
270
-	if len(msg.Params) < 3 {
271
-		rb.Add(nil, server.name, ERR_NEEDMOREPARAMS, client.Nick(), msg.Command, client.t("Not enough parameters"))
272
-		return false
273
-	}
274
-
275
-	err := server.accounts.Verify(client, account, msg.Params[2])
276
-
277
-	var code string
278
-	var message string
279
-
280
-	if err == errAccountVerificationInvalidCode {
281
-		code = "ACCOUNT_INVALID_VERIFY_CODE"
282
-		message = err.Error()
283
-	} else if err == errAccountAlreadyVerified {
284
-		code = "ACCOUNT_ALREADY_VERIFIED"
285
-		message = err.Error()
286
-	} else if err != nil {
287
-		code = "VERIFY_UNSPECIFIED_ERROR"
288
-		message = errAccountVerificationFailed.Error()
289
-	}
290
-
291
-	if err == nil {
292
-		rb.Add(nil, server.name, RPL_VERIFY_SUCCESS, client.Nick(), account, client.t("Account verification successful"))
293
-		sendSuccessfulAccountAuth(client, rb, false, false)
294
-	} else {
295
-		rb.Add(nil, server.name, "FAIL", "ACC", code, account, client.t(message))
296
-	}
297
-
298
-	return false
299
-}
300
-
301
 // AUTHENTICATE [<mechanism>|<data>|*]
113
 // AUTHENTICATE [<mechanism>|<data>|*]
302
 func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
114
 func authenticateHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool {
303
 	config := server.Config()
115
 	config := server.Config()
2300
 	oper := server.GetOperator(msg.Params[0])
2112
 	oper := server.GetOperator(msg.Params[0])
2301
 	if oper != nil {
2113
 	if oper != nil {
2302
 		if oper.Fingerprint != "" {
2114
 		if oper.Fingerprint != "" {
2303
-			if utils.CertfpsMatch(oper.Fingerprint, client.certfp) {
2115
+			if oper.Fingerprint == client.certfp {
2304
 				checkPassed = true
2116
 				checkPassed = true
2305
 			} else {
2117
 			} else {
2306
 				checkFailed = true
2118
 				checkFailed = true
2772
 			if 0 < len(info.Password) && bcrypt.CompareHashAndPassword(info.Password, givenPassword) != nil {
2584
 			if 0 < len(info.Password) && bcrypt.CompareHashAndPassword(info.Password, givenPassword) != nil {
2773
 				continue
2585
 				continue
2774
 			}
2586
 			}
2775
-			if 0 < len(info.Fingerprint) && !utils.CertfpsMatch(info.Fingerprint, client.certfp) {
2587
+			if info.Fingerprint != "" && info.Fingerprint != client.certfp {
2776
 				continue
2588
 				continue
2777
 			}
2589
 			}
2778
 
2590
 

+ 1
- 1
irc/legacy.go ファイルの表示

63
 	}
63
 	}
64
 
64
 
65
 	// upgrade credentials
65
 	// upgrade credentials
66
-	err = server.accounts.setPassword(account, passphrase)
66
+	err = server.accounts.setPassword(account, passphrase, true)
67
 	if err != nil {
67
 	if err != nil {
68
 		server.logger.Error("internal", fmt.Sprintf("could not upgrade user password: %v", err))
68
 		server.logger.Error("internal", fmt.Sprintf("could not upgrade user password: %v", err))
69
 	}
69
 	}

+ 145
- 14
irc/nickserv.go ファイルの表示

12
 	"github.com/goshuirc/irc-go/ircfmt"
12
 	"github.com/goshuirc/irc-go/ircfmt"
13
 
13
 
14
 	"github.com/oragono/oragono/irc/modes"
14
 	"github.com/oragono/oragono/irc/modes"
15
+	"github.com/oragono/oragono/irc/passwd"
15
 	"github.com/oragono/oragono/irc/sno"
16
 	"github.com/oragono/oragono/irc/sno"
16
 	"github.com/oragono/oragono/irc/utils"
17
 	"github.com/oragono/oragono/irc/utils"
17
 )
18
 )
72
 GHOST disconnects the given user from the network if they're logged in with the
73
 GHOST disconnects the given user from the network if they're logged in with the
73
 same user account, letting you reclaim your nickname.`,
74
 same user account, letting you reclaim your nickname.`,
74
 			helpShort:    `$bGHOST$b reclaims your nickname.`,
75
 			helpShort:    `$bGHOST$b reclaims your nickname.`,
76
+			enabled:      servCmdRequiresAuthEnabled,
75
 			authRequired: true,
77
 			authRequired: true,
76
 			minParams:    1,
78
 			minParams:    1,
77
 		},
79
 		},
92
 IDENTIFY lets you login to the given username using either password auth, or
94
 IDENTIFY lets you login to the given username using either password auth, or
93
 certfp (your client certificate) if a password is not given.`,
95
 certfp (your client certificate) if a password is not given.`,
94
 			helpShort: `$bIDENTIFY$b lets you login to your account.`,
96
 			helpShort: `$bIDENTIFY$b lets you login to your account.`,
97
+			enabled:   servCmdRequiresAuthEnabled,
95
 			minParams: 1,
98
 			minParams: 1,
96
 		},
99
 		},
97
 		"info": {
100
 		"info": {
179
 PASSWD lets you change your account password. You must supply your current
182
 PASSWD lets you change your account password. You must supply your current
180
 password and confirm the new one by typing it twice. If you're an IRC operator
183
 password and confirm the new one by typing it twice. If you're an IRC operator
181
 with the correct permissions, you can use PASSWD to reset someone else's
184
 with the correct permissions, you can use PASSWD to reset someone else's
182
-password by supplying their username and then the desired password.`,
185
+password by supplying their username and then the desired password. To
186
+indicate an empty password, use * instead.`,
183
 			helpShort: `$bPASSWD$b lets you change your password.`,
187
 			helpShort: `$bPASSWD$b lets you change your password.`,
184
 			enabled:   servCmdRequiresAuthEnabled,
188
 			enabled:   servCmdRequiresAuthEnabled,
185
 			minParams: 2,
189
 			minParams: 2,
259
 			minParams: 3,
263
 			minParams: 3,
260
 			capabs:    []string{"accreg"},
264
 			capabs:    []string{"accreg"},
261
 		},
265
 		},
266
+		"cert": {
267
+			handler: nsCertHandler,
268
+			help: `Syntax: $bCERT <LIST | ADD | DEL> [account] [certfp]$b
269
+
270
+CERT examines or modifies the TLS certificate fingerprints that can be used to
271
+log into an account. Specifically, $bCERT LIST$b lists the authorized
272
+fingerprints, $bCERT ADD <fingerprint>$b adds a new fingerprint, and
273
+$bCERT DEL <fingerprint>$b removes a fingerprint. If you're an IRC operator
274
+with the correct permissions, you can act on another user's account, for
275
+example with $bCERT ADD <account> <fingerprint>$b.`,
276
+			helpShort: `$bCERT$b controls a user account's certificate fingerprints`,
277
+			enabled:   servCmdRequiresAuthEnabled,
278
+			minParams: 1,
279
+		},
262
 	}
280
 	}
263
 )
281
 )
264
 
282
 
548
 }
566
 }
549
 
567
 
550
 func nsInfoHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
568
 func nsInfoHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
569
+	if !server.Config().Accounts.AuthenticationEnabled && !client.HasRoleCapabs("accreg") {
570
+		nsNotice(rb, client.t("This command has been disabled by the server administrators"))
571
+		return
572
+	}
573
+
551
 	var accountName string
574
 	var accountName string
552
 	if len(params) > 0 {
575
 	if len(params) > 0 {
553
 		nick := params[0]
576
 		nick := params[0]
659
 
682
 
660
 func nsSaregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
683
 func nsSaregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
661
 	account, passphrase := params[0], params[1]
684
 	account, passphrase := params[0], params[1]
685
+	if passphrase == "*" {
686
+		passphrase = ""
687
+	}
662
 	err := server.accounts.Register(nil, account, "admin", "", passphrase, "")
688
 	err := server.accounts.Register(nil, account, "admin", "", passphrase, "")
663
 	if err == nil {
689
 	if err == nil {
664
 		err = server.accounts.Verify(nil, account, "")
690
 		err = server.accounts.Verify(nil, account, "")
753
 	var errorMessage string
779
 	var errorMessage string
754
 
780
 
755
 	hasPrivs := client.HasRoleCapabs("accreg")
781
 	hasPrivs := client.HasRoleCapabs("accreg")
756
-	if !hasPrivs && !nsLoginThrottleCheck(client, rb) {
757
-		return
758
-	}
759
 
782
 
760
 	switch len(params) {
783
 	switch len(params) {
761
 	case 2:
784
 	case 2:
762
 		if !hasPrivs {
785
 		if !hasPrivs {
763
-			errorMessage = "Insufficient privileges"
786
+			errorMessage = `Insufficient privileges`
764
 		} else {
787
 		} else {
765
 			target, newPassword = params[0], params[1]
788
 			target, newPassword = params[0], params[1]
789
+			if newPassword == "*" {
790
+				newPassword = ""
791
+			}
766
 		}
792
 		}
767
 	case 3:
793
 	case 3:
768
 		target = client.Account()
794
 		target = client.Account()
769
 		if target == "" {
795
 		if target == "" {
770
-			errorMessage = "You're not logged into an account"
796
+			errorMessage = `You're not logged into an account`
771
 		} else if params[1] != params[2] {
797
 		} else if params[1] != params[2] {
772
-			errorMessage = "Passwords do not match"
798
+			errorMessage = `Passwords do not match`
773
 		} else {
799
 		} else {
774
-			// check that they correctly supplied the preexisting password
775
-			_, err := server.accounts.checkPassphrase(target, params[0])
800
+			if !nsLoginThrottleCheck(client, rb) {
801
+				return
802
+			}
803
+			accountData, err := server.accounts.LoadAccount(target)
776
 			if err != nil {
804
 			if err != nil {
777
-				errorMessage = "Password incorrect"
805
+				errorMessage = `You're not logged into an account`
778
 			} else {
806
 			} else {
779
-				newPassword = params[1]
807
+				hash := accountData.Credentials.PassphraseHash
808
+				if hash != nil && passwd.CompareHashAndPassword(hash, []byte(params[0])) != nil {
809
+					errorMessage = `Password incorrect`
810
+				} else {
811
+					newPassword = params[1]
812
+					if newPassword == "*" {
813
+						newPassword = ""
814
+					}
815
+				}
780
 			}
816
 			}
781
 		}
817
 		}
782
 	default:
818
 	default:
788
 		return
824
 		return
789
 	}
825
 	}
790
 
826
 
791
-	err := server.accounts.setPassword(target, newPassword)
792
-	if err == nil {
827
+	err := server.accounts.setPassword(target, newPassword, hasPrivs)
828
+	switch err {
829
+	case nil:
793
 		nsNotice(rb, client.t("Password changed"))
830
 		nsNotice(rb, client.t("Password changed"))
794
-	} else {
831
+	case errEmptyCredentials:
832
+		nsNotice(rb, client.t("You can't delete your password unless you add a certificate fingerprint"))
833
+	case errCASFailed:
834
+		nsNotice(rb, client.t("Try again later"))
835
+	default:
795
 		server.logger.Error("internal", "could not upgrade user password:", err.Error())
836
 		server.logger.Error("internal", "could not upgrade user password:", err.Error())
796
 		nsNotice(rb, client.t("Password could not be changed due to server error"))
837
 		nsNotice(rb, client.t("Password could not be changed due to server error"))
797
 	}
838
 	}
837
 		nsNotice(rb, fmt.Sprintf(client.t("Last active: %s"), session.atime.Format(time.RFC1123)))
878
 		nsNotice(rb, fmt.Sprintf(client.t("Last active: %s"), session.atime.Format(time.RFC1123)))
838
 	}
879
 	}
839
 }
880
 }
881
+
882
+func nsCertHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
883
+	verb := strings.ToLower(params[0])
884
+	params = params[1:]
885
+	var target, certfp string
886
+
887
+	switch verb {
888
+	case "list":
889
+		if 1 <= len(params) {
890
+			target = params[0]
891
+		}
892
+	case "add", "del":
893
+		if 2 <= len(params) {
894
+			target, certfp = params[0], params[1]
895
+		} else if len(params) == 1 {
896
+			certfp = params[0]
897
+		} else {
898
+			nsNotice(rb, client.t("Invalid parameters"))
899
+			return
900
+		}
901
+	default:
902
+		nsNotice(rb, client.t("Invalid parameters"))
903
+		return
904
+	}
905
+
906
+	hasPrivs := client.HasRoleCapabs("accreg")
907
+	if target != "" && !hasPrivs {
908
+		nsNotice(rb, client.t("Insufficient privileges"))
909
+		return
910
+	} else if target == "" {
911
+		target = client.Account()
912
+		if target == "" {
913
+			nsNotice(rb, client.t("You're not logged into an account"))
914
+			return
915
+		}
916
+	}
917
+
918
+	var err error
919
+	switch verb {
920
+	case "list":
921
+		accountData, err := server.accounts.LoadAccount(target)
922
+		if err == errAccountDoesNotExist {
923
+			nsNotice(rb, client.t("Account does not exist"))
924
+			return
925
+		} else if err != nil {
926
+			nsNotice(rb, client.t("An error occurred"))
927
+			return
928
+		}
929
+		certfps := accountData.Credentials.Certfps
930
+		nsNotice(rb, fmt.Sprintf(client.t("There are %d certificate fingerprint(s) authorized for account %s."), len(certfps), accountData.Name))
931
+		for i, certfp := range certfps {
932
+			nsNotice(rb, fmt.Sprintf("%d: %s", i+1, certfp))
933
+		}
934
+		return
935
+	case "add":
936
+		err = server.accounts.addRemoveCertfp(target, certfp, true, hasPrivs)
937
+	case "del":
938
+		err = server.accounts.addRemoveCertfp(target, certfp, false, hasPrivs)
939
+	}
940
+
941
+	switch err {
942
+	case nil:
943
+		if verb == "add" {
944
+			nsNotice(rb, client.t("Certificate fingerprint successfully added"))
945
+		} else {
946
+			nsNotice(rb, client.t("Certificate fingerprint successfully removed"))
947
+		}
948
+	case errNoop:
949
+		if verb == "add" {
950
+			nsNotice(rb, client.t("That certificate fingerprint was already authorized"))
951
+		} else {
952
+			nsNotice(rb, client.t("Certificate fingerprint not found"))
953
+		}
954
+	case errAccountDoesNotExist:
955
+		nsNotice(rb, client.t("Account does not exist"))
956
+	case errLimitExceeded:
957
+		nsNotice(rb, client.t("You already have too many certificate fingerprints"))
958
+	case utils.ErrInvalidCertfp:
959
+		nsNotice(rb, client.t("Invalid certificate fingerprint"))
960
+	case errCertfpAlreadyExists:
961
+		nsNotice(rb, client.t("That certificate fingerprint is already associated with another account"))
962
+	case errEmptyCredentials:
963
+		nsNotice(rb, client.t("You can't remove all your certificate fingerprints unless you add a password"))
964
+	case errCASFailed:
965
+		nsNotice(rb, client.t("Try again later"))
966
+	default:
967
+		server.logger.Error("internal", "could not modify certificates:", err.Error())
968
+		nsNotice(rb, client.t("An error occurred"))
969
+	}
970
+}

+ 11
- 9
irc/utils/crypto.go ファイルの表示

8
 	"crypto/subtle"
8
 	"crypto/subtle"
9
 	"encoding/base32"
9
 	"encoding/base32"
10
 	"encoding/base64"
10
 	"encoding/base64"
11
+	"encoding/hex"
12
+	"errors"
11
 	"strings"
13
 	"strings"
12
 )
14
 )
13
 
15
 
14
 var (
16
 var (
15
 	// slingamn's own private b32 alphabet, removing 1, l, o, and 0
17
 	// slingamn's own private b32 alphabet, removing 1, l, o, and 0
16
 	B32Encoder = base32.NewEncoding("abcdefghijkmnpqrstuvwxyz23456789").WithPadding(base32.NoPadding)
18
 	B32Encoder = base32.NewEncoding("abcdefghijkmnpqrstuvwxyz23456789").WithPadding(base32.NoPadding)
19
+
20
+	ErrInvalidCertfp = errors.New("Invalid certfp")
17
 )
21
 )
18
 
22
 
19
 const (
23
 const (
70
 	return base64.RawURLEncoding.EncodeToString(buf[:])
74
 	return base64.RawURLEncoding.EncodeToString(buf[:])
71
 }
75
 }
72
 
76
 
73
-func normalizeCertfp(certfp string) string {
74
-	return strings.ToLower(strings.Replace(certfp, ":", "", -1))
75
-}
76
-
77
-// Convenience to compare certfps as returned by different tools, e.g., openssl vs. oragono
78
-func CertfpsMatch(storedCertfp, suppliedCertfp string) bool {
79
-	if storedCertfp == "" {
80
-		return false
77
+// Normalize openssl-formatted certfp's to oragono's format
78
+func NormalizeCertfp(certfp string) (result string, err error) {
79
+	result = strings.ToLower(strings.Replace(certfp, ":", "", -1))
80
+	decoded, err := hex.DecodeString(result)
81
+	if err != nil || len(decoded) != 32 {
82
+		return "", ErrInvalidCertfp
81
 	}
83
 	}
82
-	return normalizeCertfp(storedCertfp) == normalizeCertfp(suppliedCertfp)
84
+	return
83
 }
85
 }

+ 17
- 11
irc/utils/crypto_test.go ファイルの表示

85
 func TestCertfpComparisons(t *testing.T) {
85
 func TestCertfpComparisons(t *testing.T) {
86
 	opensslFP := "3D:6B:11:BF:B4:05:C3:F8:4B:38:CD:30:38:FB:EC:01:71:D5:03:54:79:04:07:88:4C:A5:5D:23:41:85:66:C9"
86
 	opensslFP := "3D:6B:11:BF:B4:05:C3:F8:4B:38:CD:30:38:FB:EC:01:71:D5:03:54:79:04:07:88:4C:A5:5D:23:41:85:66:C9"
87
 	oragonoFP := "3d6b11bfb405c3f84b38cd3038fbec0171d50354790407884ca55d23418566c9"
87
 	oragonoFP := "3d6b11bfb405c3f84b38cd3038fbec0171d50354790407884ca55d23418566c9"
88
-	badFP := "3d6b11bfb405c3f84b38cd3038fbec0171d50354790407884ca55d23418566c8"
89
-	if !CertfpsMatch(opensslFP, oragonoFP) {
90
-		t.Error("these certs should match")
91
-	}
92
-	if !CertfpsMatch(oragonoFP, opensslFP) {
93
-		t.Error("these certs should match")
94
-	}
95
-	if CertfpsMatch("", "") {
96
-		t.Error("empty stored certfp should not match empty provided certfp")
88
+	badFP := "3d6b11bfb405c3f84b38cd3038fbec0171d50354790407884ca55d23418566c"
89
+	badFP2 := "*"
90
+
91
+	normalizedOpenssl, err := NormalizeCertfp(opensslFP)
92
+	assertEqual(err, nil, t)
93
+	assertEqual(normalizedOpenssl, oragonoFP, t)
94
+
95
+	normalizedOragono, err := NormalizeCertfp(oragonoFP)
96
+	assertEqual(err, nil, t)
97
+	assertEqual(normalizedOragono, oragonoFP, t)
98
+
99
+	_, err = NormalizeCertfp(badFP)
100
+	if err == nil {
101
+		t.Errorf("corrupt fp should fail normalization")
97
 	}
102
 	}
98
-	if CertfpsMatch(opensslFP, badFP) {
99
-		t.Error("these certs should not match")
103
+	_, err = NormalizeCertfp(badFP2)
104
+	if err == nil {
105
+		t.Errorf("corrupt fp should fail normalization")
100
 	}
106
 	}
101
 }
107
 }

+ 3
- 3
oragono.yaml ファイルの表示

244
 
244
 
245
 # account options
245
 # account options
246
 accounts:
246
 accounts:
247
+    # is account authentication enabled, i.e., can users log into existing accounts?
248
+    authentication-enabled: true
249
+
247
     # account registration
250
     # account registration
248
     registration:
251
     registration:
249
         # can users register new accounts for themselves? if this is false, operators with
252
         # can users register new accounts for themselves? if this is false, operators with
271
         #         password: ""
274
         #         password: ""
272
         #         sender: "admin@my.network"
275
         #         sender: "admin@my.network"
273
 
276
 
274
-    # is account authentication enabled?
275
-    authentication-enabled: true
276
-
277
     # throttle account login attempts (to prevent either password guessing, or DoS
277
     # throttle account login attempts (to prevent either password guessing, or DoS
278
     # attacks on the server aimed at forcing repeated expensive bcrypt computations)
278
     # attacks on the server aimed at forcing repeated expensive bcrypt computations)
279
     login-throttling:
279
     login-throttling:

読み込み中…
キャンセル
保存