Browse Source

first draft of atheme migration code

tags/v2.4.0-rc1
Shivaram Lingamneni 3 years ago
parent
commit
7a6413ea2c

+ 1
- 0
Makefile View File

@@ -27,6 +27,7 @@ test:
27 27
 	cd irc/email && go test . && go vet .
28 28
 	cd irc/history && go test . && go vet .
29 29
 	cd irc/isupport && go test . && go vet .
30
+	cd irc/migrations && go test . && go vet .
30 31
 	cd irc/modes && go test . && go vet .
31 32
 	cd irc/mysql && go test . && go vet .
32 33
 	cd irc/passwd && go test . && go vet .

+ 111
- 0
distrib/atheme/atheme2json.py View File

@@ -0,0 +1,111 @@
1
+import json
2
+import logging
3
+import sys
4
+from collections import defaultdict
5
+
6
+def to_unixnano(timestamp):
7
+    return int(timestamp) * (10**9)
8
+
9
+def convert(infile):
10
+    out = {
11
+        'version': 1,
12
+        'source': 'atheme',
13
+        'users': defaultdict(dict),
14
+        'channels': defaultdict(dict),
15
+    }
16
+
17
+    channel_to_founder = defaultdict(lambda: (None, None))
18
+
19
+    for line in infile:
20
+        line = line.strip()
21
+        parts = line.split()
22
+        category = parts[0]
23
+        if category == 'MU':
24
+            # user account
25
+            # MU AAAAAAAAB shivaram $1$hcspif$nCm4r3S14Me9ifsOPGuJT. user@example.com 1600134392 1600467343 +sC default
26
+            name = parts[2]
27
+            user = {'name': name, 'hash': parts[3], 'email': parts[4], 'registeredAt': to_unixnano(parts[5])}
28
+            out['users'][name].update(user)
29
+            pass
30
+        elif category == 'MN':
31
+            # grouped nick
32
+            # MN shivaram slingamn 1600218831 1600467343
33
+            username, groupednick = parts[1], parts[2]
34
+            if username != groupednick:
35
+                user = out['users'][username]
36
+                if 'additionalNicks' not in user:
37
+                    user['additionalNicks'] = []
38
+                user['additionalNicks'].append(groupednick)
39
+        elif category == 'MDU':
40
+            if parts[2] == 'private:usercloak':
41
+                username = parts[1]
42
+                out['users'][username]['vhost'] = parts[3]
43
+        elif category == 'MC':
44
+            # channel registration
45
+            # MC #mychannel 1600134478 1600467343 +v 272 0 0
46
+            chname = parts[1]
47
+            out['channels'][chname].update({'name': chname, 'registeredAt': to_unixnano(parts[2])})
48
+        elif category == 'MDC':
49
+            # auxiliary data for a channel registration
50
+            # MDC #mychannel private:topic:setter s
51
+            # MDC #mychannel private:topic:text hi again
52
+            # MDC #mychannel private:topic:ts 1600135864
53
+            chname = parts[1]
54
+            category = parts[2]
55
+            if category == 'private:topic:text':
56
+                out['channels'][chname]['topic'] = parts[3]
57
+            elif category == 'private:topic:setter':
58
+                out['channels'][chname]['topicSetBy'] = parts[3]
59
+            elif category == 'private:topic:ts':
60
+                out['channels'][chname]['topicSetAt'] = to_unixnano(parts[3])
61
+        elif category == 'CA':
62
+            # channel access lists
63
+            # CA #mychannel shivaram +AFORafhioqrstv 1600134478 shivaram
64
+            chname, username, flags, set_at = parts[1], parts[2], parts[3], int(parts[4])
65
+            chname = parts[1]
66
+            chdata = out['channels'][chname]
67
+            flags = parts[3]
68
+            set_at = int(parts[4])
69
+            if 'amode' not in chdata:
70
+                chdata['amode'] = {}
71
+            if 'q' in flags:
72
+                # there can only be one founder
73
+                preexisting_founder, preexisting_set_at = channel_to_founder[chname]
74
+                if preexisting_founder is None or set_at < preexisting_set_at:
75
+                    chdata['founder'] = username
76
+                    channel_to_founder[chname] = (username, set_at)
77
+                # but multiple people can receive the 'q' amode
78
+                chdata['amode'][username] = ord('q')
79
+            elif 'a' in flags:
80
+                chdata['amode'][username] = ord('a')
81
+            elif 'o' in flags:
82
+                chdata['amode'][username] = ord('o')
83
+            elif 'h' in flags:
84
+                chdata['amode'][username] = ord('h')
85
+            elif 'v' in flags:
86
+                chdata['amode'][username] = ord('v')
87
+        else:
88
+            pass
89
+
90
+    # do some basic integrity checks
91
+    for chname, chdata in out['channels'].items():
92
+        founder = chdata.get('founder')
93
+        if founder not in out['users']:
94
+            raise ValueError("no user corresponding to channel founder", chname, chdata.get('founder'))
95
+        if 'registeredChannels' not in out['users'][founder]:
96
+            out['users'][founder]['registeredChannels'] = []
97
+        out['users'][founder]['registeredChannels'].append(chname)
98
+
99
+    return out
100
+
101
+def main():
102
+    if len(sys.argv) != 3:
103
+        raise Exception("Usage: atheme2json.py atheme_db output.json")
104
+    with open(sys.argv[1]) as infile:
105
+        output = convert(infile)
106
+        with open(sys.argv[2], 'w') as outfile:
107
+            json.dump(output, outfile)
108
+
109
+if __name__ == '__main__':
110
+    logging.basicConfig()
111
+    sys.exit(main())

+ 1
- 0
go.mod View File

@@ -4,6 +4,7 @@ go 1.15
4 4
 
5 5
 require (
6 6
 	code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48
7
+	github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
7 8
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
8 9
 	github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
9 10
 	github.com/go-sql-driver/mysql v1.5.0

+ 2
- 0
go.sum View File

@@ -1,6 +1,8 @@
1 1
 code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48 h1:/EMHruHCFXR9xClkGV/t0rmHrdhX4+trQUcBqjwc9xE=
2 2
 code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48/go.mod h1:wN/zk7mhREp/oviagqUXY3EwuHhWyOvAdsn5Y4CzOrc=
3 3
 github.com/DanielOaks/go-idn v0.0.0-20160120021903-76db0e10dc65/go.mod h1:GYIaL2hleNQvfMUBTes1Zd/lDTyI/p2hv3kYB4jssyU=
4
+github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw=
5
+github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo=
4 6
 github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
5 7
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 8
 github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=

+ 30
- 3
irc/accounts.go View File

@@ -18,6 +18,7 @@ import (
18 18
 
19 19
 	"github.com/oragono/oragono/irc/connection_limits"
20 20
 	"github.com/oragono/oragono/irc/email"
21
+	"github.com/oragono/oragono/irc/migrations"
21 22
 	"github.com/oragono/oragono/irc/modes"
22 23
 	"github.com/oragono/oragono/irc/passwd"
23 24
 	"github.com/oragono/oragono/irc/utils"
@@ -1047,17 +1048,35 @@ func (am *AccountManager) checkPassphrase(accountName, passphrase string) (accou
1047 1048
 
1048 1049
 	switch account.Credentials.Version {
1049 1050
 	case 0:
1050
-		err = handleLegacyPasswordV0(am.server, accountName, account.Credentials, passphrase)
1051
+		err = am.checkLegacyPassphrase(migrations.CheckOragonoPassphraseV0, accountName, account.Credentials.PassphraseHash, passphrase)
1051 1052
 	case 1:
1052 1053
 		if passwd.CompareHashAndPassword(account.Credentials.PassphraseHash, []byte(passphrase)) != nil {
1053 1054
 			err = errAccountInvalidCredentials
1054 1055
 		}
1056
+	case -1:
1057
+		err = am.checkLegacyPassphrase(migrations.CheckAthemePassphrase, accountName, account.Credentials.PassphraseHash, passphrase)
1055 1058
 	default:
1056 1059
 		err = errAccountInvalidCredentials
1057 1060
 	}
1058 1061
 	return
1059 1062
 }
1060 1063
 
1064
+func (am *AccountManager) checkLegacyPassphrase(check migrations.PassphraseCheck, account string, hash []byte, passphrase string) (err error) {
1065
+	err = check(hash, []byte(passphrase))
1066
+	if err != nil {
1067
+		if err == migrations.ErrHashInvalid {
1068
+			am.server.logger.Error("internal", "invalid legacy credentials for account", account)
1069
+		}
1070
+		return errAccountInvalidCredentials
1071
+	}
1072
+	// re-hash the passphrase with the latest algorithm
1073
+	err = am.setPassword(account, passphrase, true)
1074
+	if err != nil {
1075
+		am.server.logger.Error("internal", "could not upgrade user password", err.Error())
1076
+	}
1077
+	return nil
1078
+}
1079
+
1061 1080
 func (am *AccountManager) loadWithAutocreation(accountName string, autocreate bool) (account ClientAccount, err error) {
1062 1081
 	account, err = am.LoadAccount(accountName)
1063 1082
 	if err == errAccountDoesNotExist && autocreate {
@@ -1872,10 +1891,18 @@ var (
1872 1891
 	}
1873 1892
 )
1874 1893
 
1894
+type CredentialsVersion int
1895
+
1896
+const (
1897
+	CredentialsLegacy     CredentialsVersion = 0
1898
+	CredentialsSHA3Bcrypt CredentialsVersion = 1
1899
+	// negative numbers for migration
1900
+	CredentialsAtheme = -1
1901
+)
1902
+
1875 1903
 // AccountCredentials stores the various methods for verifying accounts.
1876 1904
 type AccountCredentials struct {
1877
-	Version        uint
1878
-	PassphraseSalt []byte // legacy field, not used by v1 and later
1905
+	Version        CredentialsVersion
1879 1906
 	PassphraseHash []byte
1880 1907
 	Certfps        []string
1881 1908
 }

+ 6
- 6
irc/channelreg.go View File

@@ -233,11 +233,11 @@ func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info RegisteredC
233 233
 		info = RegisteredChannel{
234 234
 			Name:           name,
235 235
 			NameCasefolded: nameCasefolded,
236
-			RegisteredAt:   time.Unix(regTimeInt, 0).UTC(),
236
+			RegisteredAt:   time.Unix(0, regTimeInt).UTC(),
237 237
 			Founder:        founder,
238 238
 			Topic:          topic,
239 239
 			TopicSetBy:     topicSetBy,
240
-			TopicSetTime:   time.Unix(topicSetTimeInt, 0).UTC(),
240
+			TopicSetTime:   time.Unix(0, topicSetTimeInt).UTC(),
241 241
 			Key:            password,
242 242
 			Modes:          modeSlice,
243 243
 			Bans:           banlist,
@@ -273,11 +273,11 @@ func (reg *ChannelRegistry) deleteChannel(tx *buntdb.Tx, key string, info Regist
273 273
 	if err == nil {
274 274
 		regTime, _ := tx.Get(fmt.Sprintf(keyChannelRegTime, key))
275 275
 		regTimeInt, _ := strconv.ParseInt(regTime, 10, 64)
276
-		registeredAt := time.Unix(regTimeInt, 0).UTC()
276
+		registeredAt := time.Unix(0, regTimeInt).UTC()
277 277
 		founder, _ := tx.Get(fmt.Sprintf(keyChannelFounder, key))
278 278
 
279 279
 		// to see if we're deleting the right channel, confirm the founder and the registration time
280
-		if founder == info.Founder && registeredAt.Unix() == info.RegisteredAt.Unix() {
280
+		if founder == info.Founder && registeredAt == info.RegisteredAt {
281 281
 			for _, keyFmt := range channelKeyStrings {
282 282
 				tx.Delete(fmt.Sprintf(keyFmt, key))
283 283
 			}
@@ -339,13 +339,13 @@ func (reg *ChannelRegistry) saveChannel(tx *buntdb.Tx, channelInfo RegisteredCha
339 339
 	if includeFlags&IncludeInitial != 0 {
340 340
 		tx.Set(fmt.Sprintf(keyChannelExists, channelKey), "1", nil)
341 341
 		tx.Set(fmt.Sprintf(keyChannelName, channelKey), channelInfo.Name, nil)
342
-		tx.Set(fmt.Sprintf(keyChannelRegTime, channelKey), strconv.FormatInt(channelInfo.RegisteredAt.Unix(), 10), nil)
342
+		tx.Set(fmt.Sprintf(keyChannelRegTime, channelKey), strconv.FormatInt(channelInfo.RegisteredAt.UnixNano(), 10), nil)
343 343
 		tx.Set(fmt.Sprintf(keyChannelFounder, channelKey), channelInfo.Founder, nil)
344 344
 	}
345 345
 
346 346
 	if includeFlags&IncludeTopic != 0 {
347 347
 		tx.Set(fmt.Sprintf(keyChannelTopic, channelKey), channelInfo.Topic, nil)
348
-		tx.Set(fmt.Sprintf(keyChannelTopicSetTime, channelKey), strconv.FormatInt(channelInfo.TopicSetTime.Unix(), 10), nil)
348
+		tx.Set(fmt.Sprintf(keyChannelTopicSetTime, channelKey), strconv.FormatInt(channelInfo.TopicSetTime.UnixNano(), 10), nil)
349 349
 		tx.Set(fmt.Sprintf(keyChannelTopicSetBy, channelKey), channelInfo.TopicSetBy, nil)
350 350
 	}
351 351
 

+ 124
- 8
irc/database.go View File

@@ -5,6 +5,7 @@
5 5
 package irc
6 6
 
7 7
 import (
8
+	"encoding/base64"
8 9
 	"encoding/json"
9 10
 	"fmt"
10 11
 	"log"
@@ -23,7 +24,7 @@ const (
23 24
 	// 'version' of the database schema
24 25
 	keySchemaVersion = "db.version"
25 26
 	// latest schema of the db
26
-	latestDbSchema = "12"
27
+	latestDbSchema = "14"
27 28
 
28 29
 	keyCloakSecret = "crypto.cloak_secret"
29 30
 )
@@ -39,19 +40,26 @@ type SchemaChange struct {
39 40
 // maps an initial version to a schema change capable of upgrading it
40 41
 var schemaChanges map[string]SchemaChange
41 42
 
42
-// InitDB creates the database, implementing the `oragono initdb` command.
43
-func InitDB(path string) {
43
+func checkDBReadyForInit(path string) error {
44 44
 	_, err := os.Stat(path)
45 45
 	if err == nil {
46
-		log.Fatal("Datastore already exists (delete it manually to continue): ", path)
46
+		return fmt.Errorf("Datastore already exists (delete it manually to continue): %s", path)
47 47
 	} else if !os.IsNotExist(err) {
48
-		log.Fatal("Datastore path is inaccessible: ", err.Error())
48
+		return fmt.Errorf("Datastore path %s is inaccessible: %w", path, err)
49 49
 	}
50
+	return nil
51
+}
50 52
 
51
-	err = initializeDB(path)
52
-	if err != nil {
53
-		log.Fatal("Could not save datastore: ", err.Error())
53
+// InitDB creates the database, implementing the `oragono initdb` command.
54
+func InitDB(path string) error {
55
+	if err := checkDBReadyForInit(path); err != nil {
56
+		return err
57
+	}
58
+
59
+	if err := initializeDB(path); err != nil {
60
+		return fmt.Errorf("Could not save datastore: %w", err)
54 61
 	}
62
+	return nil
55 63
 }
56 64
 
57 65
 // internal database initialization code
@@ -686,6 +694,104 @@ func schemaChangeV11ToV12(config *Config, tx *buntdb.Tx) error {
686 694
 	return nil
687 695
 }
688 696
 
697
+type accountCredsLegacyV13 struct {
698
+	Version        CredentialsVersion
699
+	PassphraseHash []byte
700
+	Certfps        []string
701
+}
702
+
703
+// see #212 / #284. this packs the legacy salts into a single passphrase hash,
704
+// allowing legacy passphrases to be verified using the new API `checkLegacyPassphrase`.
705
+func schemaChangeV12ToV13(config *Config, tx *buntdb.Tx) error {
706
+	salt, err := tx.Get("crypto.salt")
707
+	if err != nil {
708
+		return nil // no change required
709
+	}
710
+	tx.Delete("crypto.salt")
711
+	rawSalt, err := base64.StdEncoding.DecodeString(salt)
712
+	if err != nil {
713
+		return nil // just throw away the creds at this point
714
+	}
715
+	prefix := "account.credentials "
716
+	var accounts []string
717
+	var credentials []accountCredsLegacyV13
718
+	tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
719
+		if !strings.HasPrefix(key, prefix) {
720
+			return false
721
+		}
722
+		account := strings.TrimPrefix(key, prefix)
723
+
724
+		var credsOld accountCredsLegacyV9
725
+		err = json.Unmarshal([]byte(value), &credsOld)
726
+		if err != nil {
727
+			return true
728
+		}
729
+		// skip if these aren't legacy creds!
730
+		if credsOld.Version != 0 {
731
+			return true
732
+		}
733
+
734
+		var credsNew accountCredsLegacyV13
735
+		credsNew.Version = 0 // mark hash for migration
736
+		credsNew.Certfps = credsOld.Certfps
737
+		credsNew.PassphraseHash = append(credsNew.PassphraseHash, rawSalt...)
738
+		credsNew.PassphraseHash = append(credsNew.PassphraseHash, credsOld.PassphraseSalt...)
739
+		credsNew.PassphraseHash = append(credsNew.PassphraseHash, credsOld.PassphraseHash...)
740
+
741
+		accounts = append(accounts, account)
742
+		credentials = append(credentials, credsNew)
743
+		return true
744
+	})
745
+
746
+	for i, account := range accounts {
747
+		bytesOut, err := json.Marshal(credentials[i])
748
+		if err != nil {
749
+			return err
750
+		}
751
+		_, _, err = tx.Set(prefix+account, string(bytesOut), nil)
752
+		if err != nil {
753
+			return err
754
+		}
755
+	}
756
+
757
+	return nil
758
+}
759
+
760
+// channel registration time and topic set time at nanosecond resolution
761
+func schemaChangeV13ToV14(config *Config, tx *buntdb.Tx) error {
762
+	prefix := "channel.registered.time "
763
+	var channels, times []string
764
+	tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
765
+		if !strings.HasPrefix(key, prefix) {
766
+			return false
767
+		}
768
+		channel := strings.TrimPrefix(key, prefix)
769
+		channels = append(channels, channel)
770
+		times = append(times, value)
771
+		return true
772
+	})
773
+
774
+	billion := int64(time.Second)
775
+	for i, channel := range channels {
776
+		regTime, err := strconv.ParseInt(times[i], 10, 64)
777
+		if err != nil {
778
+			log.Printf("corrupt registration time entry for %s: %v\n", channel, err)
779
+			continue
780
+		}
781
+		regTime = regTime * billion
782
+		tx.Set(prefix+channel, strconv.FormatInt(regTime, 10), nil)
783
+
784
+		topicTimeKey := "channel.topic.settime " + channel
785
+		topicSetAt, err := tx.Get(topicTimeKey)
786
+		if err == nil {
787
+			if setTime, err := strconv.ParseInt(topicSetAt, 10, 64); err == nil {
788
+				tx.Set(topicTimeKey, strconv.FormatInt(setTime*billion, 10), nil)
789
+			}
790
+		}
791
+	}
792
+	return nil
793
+}
794
+
689 795
 func init() {
690 796
 	allChanges := []SchemaChange{
691 797
 		{
@@ -743,6 +849,16 @@ func init() {
743 849
 			TargetVersion:  "12",
744 850
 			Changer:        schemaChangeV11ToV12,
745 851
 		},
852
+		{
853
+			InitialVersion: "12",
854
+			TargetVersion:  "13",
855
+			Changer:        schemaChangeV12ToV13,
856
+		},
857
+		{
858
+			InitialVersion: "13",
859
+			TargetVersion:  "14",
860
+			Changer:        schemaChangeV13ToV14,
861
+		},
746 862
 	}
747 863
 
748 864
 	// build the index

+ 155
- 0
irc/import.go View File

@@ -0,0 +1,155 @@
1
+// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2
+// released under the MIT license
3
+
4
+package irc
5
+
6
+import (
7
+	"encoding/json"
8
+	"fmt"
9
+	"io/ioutil"
10
+	"log"
11
+	"strconv"
12
+	"strings"
13
+
14
+	"github.com/tidwall/buntdb"
15
+
16
+	"github.com/oragono/oragono/irc/utils"
17
+)
18
+
19
+type userImport struct {
20
+	Name               string
21
+	Hash               string
22
+	Email              string
23
+	RegisteredAt       int64 `json:"registeredAt"`
24
+	Vhost              string
25
+	AdditionalNicks    []string `json:"additionalNicks"`
26
+	RegisteredChannels []string
27
+}
28
+
29
+type channelImport struct {
30
+	Name         string
31
+	Founder      string
32
+	RegisteredAt int64 `json:"registeredAt"`
33
+	Topic        string
34
+	TopicSetBy   string `json:"topicSetBy"`
35
+	TopicSetAt   int64  `json:"topicSetAt"`
36
+	Amode        map[string]int
37
+}
38
+
39
+type databaseImport struct {
40
+	Version  int
41
+	Source   string
42
+	Users    map[string]userImport
43
+	Channels map[string]channelImport
44
+}
45
+
46
+func doImportAthemeDB(config *Config, dbImport databaseImport, tx *buntdb.Tx) (err error) {
47
+	requiredVersion := 1
48
+	if dbImport.Version != requiredVersion {
49
+		return fmt.Errorf("unsupported version of the db for import: version %d is required", requiredVersion)
50
+	}
51
+
52
+	// produce a hardcoded version of the database schema
53
+	// XXX instead of referencing, e.g., keyAccountExists, we should write in the string literal
54
+	// (to ensure that no matter what code changes happen elsewhere, we're still producing a
55
+	// version 14 db)
56
+	tx.Set(keySchemaVersion, "14", nil)
57
+	tx.Set(keyCloakSecret, utils.GenerateSecretKey(), nil)
58
+
59
+	for username, userInfo := range dbImport.Users {
60
+		cfUsername, err := CasefoldName(username)
61
+		if err != nil {
62
+			log.Printf("invalid username %s: %v", username, err)
63
+			continue
64
+		}
65
+		credentials := AccountCredentials{
66
+			Version:        CredentialsAtheme,
67
+			PassphraseHash: []byte(userInfo.Hash),
68
+		}
69
+		marshaledCredentials, err := json.Marshal(&credentials)
70
+		if err != nil {
71
+			log.Printf("invalid credentials for %s: %v", username, err)
72
+			continue
73
+		}
74
+		tx.Set(fmt.Sprintf(keyAccountExists, cfUsername), "1", nil)
75
+		tx.Set(fmt.Sprintf(keyAccountVerified, cfUsername), "1", nil)
76
+		tx.Set(fmt.Sprintf(keyAccountName, cfUsername), userInfo.Name, nil)
77
+		tx.Set(fmt.Sprintf(keyAccountCallback, cfUsername), "mailto:"+userInfo.Email, nil)
78
+		tx.Set(fmt.Sprintf(keyAccountCredentials, cfUsername), string(marshaledCredentials), nil)
79
+		tx.Set(fmt.Sprintf(keyAccountRegTime, cfUsername), strconv.FormatInt(userInfo.RegisteredAt, 10), nil)
80
+		if userInfo.Vhost != "" {
81
+			tx.Set(fmt.Sprintf(keyAccountVHost, cfUsername), userInfo.Vhost, nil)
82
+		}
83
+		if len(userInfo.AdditionalNicks) != 0 {
84
+			tx.Set(fmt.Sprintf(keyAccountAdditionalNicks, cfUsername), marshalReservedNicks(userInfo.AdditionalNicks), nil)
85
+		}
86
+		if len(userInfo.RegisteredChannels) != 0 {
87
+			tx.Set(fmt.Sprintf(keyAccountChannels, cfUsername), strings.Join(userInfo.RegisteredChannels, ","), nil)
88
+		}
89
+	}
90
+
91
+	for chname, chInfo := range dbImport.Channels {
92
+		cfchname, err := CasefoldChannel(chname)
93
+		if err != nil {
94
+			log.Printf("invalid channel name %s: %v", chname, err)
95
+			continue
96
+		}
97
+		tx.Set(fmt.Sprintf(keyChannelExists, cfchname), "1", nil)
98
+		tx.Set(fmt.Sprintf(keyChannelName, cfchname), chname, nil)
99
+		tx.Set(fmt.Sprintf(keyChannelRegTime, cfchname), strconv.FormatInt(chInfo.RegisteredAt, 10), nil)
100
+		tx.Set(fmt.Sprintf(keyChannelFounder, cfchname), chInfo.Founder, nil)
101
+		if chInfo.Topic != "" {
102
+			tx.Set(fmt.Sprintf(keyChannelTopic, cfchname), chInfo.Topic, nil)
103
+			tx.Set(fmt.Sprintf(keyChannelTopicSetTime, cfchname), strconv.FormatInt(chInfo.TopicSetAt, 10), nil)
104
+			tx.Set(fmt.Sprintf(keyChannelTopicSetBy, cfchname), chInfo.TopicSetBy, nil)
105
+		}
106
+		if len(chInfo.Amode) != 0 {
107
+			m, err := json.Marshal(chInfo.Amode)
108
+			if err == nil {
109
+				tx.Set(fmt.Sprintf(keyChannelAccountToUMode, cfchname), string(m), nil)
110
+			} else {
111
+				log.Printf("couldn't serialize amodes for %s: %v", chname, err)
112
+			}
113
+		}
114
+	}
115
+
116
+	return nil
117
+}
118
+
119
+func doImportDB(config *Config, dbImport databaseImport, tx *buntdb.Tx) (err error) {
120
+	switch dbImport.Source {
121
+	case "atheme":
122
+		return doImportAthemeDB(config, dbImport, tx)
123
+	default:
124
+		return fmt.Errorf("only imports from atheme are currently supported")
125
+	}
126
+}
127
+
128
+func ImportDB(config *Config, infile string) (err error) {
129
+	data, err := ioutil.ReadFile(infile)
130
+	if err != nil {
131
+		return
132
+	}
133
+
134
+	var dbImport databaseImport
135
+	err = json.Unmarshal(data, &dbImport)
136
+	if err != nil {
137
+		return err
138
+	}
139
+
140
+	err = checkDBReadyForInit(config.Datastore.Path)
141
+	if err != nil {
142
+		return err
143
+	}
144
+
145
+	db, err := buntdb.Open(config.Datastore.Path)
146
+	if err != nil {
147
+		return err
148
+	}
149
+
150
+	performImport := func(tx *buntdb.Tx) (err error) {
151
+		return doImportDB(config, dbImport, tx)
152
+	}
153
+
154
+	return db.Update(performImport)
155
+}

+ 0
- 45
irc/legacy.go View File

@@ -5,10 +5,6 @@ package irc
5 5
 import (
6 6
 	"encoding/base64"
7 7
 	"errors"
8
-	"fmt"
9
-
10
-	"github.com/tidwall/buntdb"
11
-	"golang.org/x/crypto/bcrypt"
12 8
 )
13 9
 
14 10
 var (
@@ -29,44 +25,3 @@ func decodeLegacyPasswordHash(hash string) ([]byte, error) {
29 25
 		return nil, errInvalidPasswordHash
30 26
 	}
31 27
 }
32
-
33
-// helper to check a version 0 password hash, with global and per-passphrase salts
34
-func checkLegacyPasswordV0(hashedPassword, globalSalt, passphraseSalt []byte, passphrase string) error {
35
-	var assembledPasswordBytes []byte
36
-	assembledPasswordBytes = append(assembledPasswordBytes, globalSalt...)
37
-	assembledPasswordBytes = append(assembledPasswordBytes, '-')
38
-	assembledPasswordBytes = append(assembledPasswordBytes, passphraseSalt...)
39
-	assembledPasswordBytes = append(assembledPasswordBytes, '-')
40
-	assembledPasswordBytes = append(assembledPasswordBytes, []byte(passphrase)...)
41
-	return bcrypt.CompareHashAndPassword(hashedPassword, assembledPasswordBytes)
42
-}
43
-
44
-// checks a version 0 password hash; if successful, upgrades the database entry to version 1
45
-func handleLegacyPasswordV0(server *Server, account string, credentials AccountCredentials, passphrase string) (err error) {
46
-	var globalSaltString string
47
-	err = server.store.View(func(tx *buntdb.Tx) (err error) {
48
-		globalSaltString, err = tx.Get("crypto.salt")
49
-		return err
50
-	})
51
-	if err != nil {
52
-		return err
53
-	}
54
-	globalSalt, err := base64.StdEncoding.DecodeString(globalSaltString)
55
-	if err != nil {
56
-		return err
57
-	}
58
-
59
-	err = checkLegacyPasswordV0(credentials.PassphraseHash, globalSalt, credentials.PassphraseSalt, passphrase)
60
-	if err != nil {
61
-		// invalid password
62
-		return err
63
-	}
64
-
65
-	// upgrade credentials
66
-	err = server.accounts.setPassword(account, passphrase, true)
67
-	if err != nil {
68
-		server.logger.Error("internal", fmt.Sprintf("could not upgrade user password: %v", err))
69
-	}
70
-
71
-	return nil
72
-}

+ 20
- 0
irc/migrations/legacy.go View File

@@ -0,0 +1,20 @@
1
+package migrations
2
+
3
+import (
4
+	"golang.org/x/crypto/bcrypt"
5
+)
6
+
7
+// See the v12-to-v13 schema change. The format of this hash is:
8
+// 30 bytes of global salt, 30 bytes of per-passphrase salt, then the bcrypt hash
9
+func CheckOragonoPassphraseV0(hash, passphrase []byte) error {
10
+	globalSalt := hash[:30]
11
+	passphraseSalt := hash[30:60]
12
+	bcryptHash := hash[60:]
13
+	assembledPasswordBytes := make([]byte, 0, 60+len(passphrase)+2)
14
+	assembledPasswordBytes = append(assembledPasswordBytes, globalSalt...)
15
+	assembledPasswordBytes = append(assembledPasswordBytes, '-')
16
+	assembledPasswordBytes = append(assembledPasswordBytes, passphraseSalt...)
17
+	assembledPasswordBytes = append(assembledPasswordBytes, '-')
18
+	assembledPasswordBytes = append(assembledPasswordBytes, passphrase...)
19
+	return bcrypt.CompareHashAndPassword(bcryptHash, assembledPasswordBytes)
20
+}

+ 183
- 0
irc/migrations/passwords.go View File

@@ -0,0 +1,183 @@
1
+package migrations
2
+
3
+import (
4
+	"bytes"
5
+	"crypto/hmac"
6
+	"crypto/md5"
7
+	"crypto/sha1"
8
+	"crypto/sha256"
9
+	"crypto/sha512"
10
+	"crypto/subtle"
11
+	"encoding/base64"
12
+	"encoding/hex"
13
+	"errors"
14
+	"hash"
15
+	"strconv"
16
+
17
+	"github.com/GehirnInc/crypt/md5_crypt"
18
+	"golang.org/x/crypto/pbkdf2"
19
+)
20
+
21
+var (
22
+	ErrHashInvalid     = errors.New("password hash invalid for algorithm")
23
+	ErrHashCheckFailed = errors.New("passphrase did not match stored hash")
24
+
25
+	hmacServerKeyText    = []byte("Server Key")
26
+	athemePBKDF2V2Prefix = []byte("$z")
27
+)
28
+
29
+type PassphraseCheck func(hash, passphrase []byte) (err error)
30
+
31
+func CheckAthemePassphrase(hash, passphrase []byte) (err error) {
32
+	if len(hash) < 60 {
33
+		return checkAthemePosixCrypt(hash, passphrase)
34
+	} else if bytes.HasPrefix(hash, athemePBKDF2V2Prefix) {
35
+		return checkAthemePBKDF2V2(hash, passphrase)
36
+	} else {
37
+		return checkAthemePBKDF2(hash, passphrase)
38
+	}
39
+}
40
+
41
+func checkAthemePosixCrypt(hash, passphrase []byte) (err error) {
42
+	// crypto/posix: the platform's crypt(3) function
43
+	// MD5 on linux, DES on MacOS: forget MacOS
44
+	md5crypt := md5_crypt.New()
45
+	return md5crypt.Verify(string(hash), []byte(passphrase))
46
+}
47
+
48
+type pbkdf2v2Algo struct {
49
+	Hash       func() hash.Hash
50
+	OutputSize int
51
+	SCRAM      bool
52
+	SaltB64    bool
53
+}
54
+
55
+func athemePBKDF2V2ParseAlgo(algo string) (result pbkdf2v2Algo, err error) {
56
+	// https://github.com/atheme/atheme/blob/a11e85efc67d86fc4738e3e2a4f220bfa69153f0/include/atheme/pbkdf2.h#L34-L52
57
+	algoInt, err := strconv.Atoi(algo)
58
+	if err != nil {
59
+		return result, ErrHashInvalid
60
+	}
61
+	hashCode := algoInt % 10
62
+	algoCode := algoInt - hashCode
63
+
64
+	switch algoCode {
65
+	case 0:
66
+		// e.g., #define PBKDF2_PRF_HMAC_MD5             3U
67
+		// no SCRAM, no SHA256
68
+	case 20:
69
+		// e.g., #define PBKDF2_PRF_HMAC_MD5_S64         23U
70
+		// no SCRAM, base64
71
+		result.SaltB64 = true
72
+	case 40:
73
+		// e.g., #define PBKDF2_PRF_SCRAM_MD5            43U
74
+		// SCRAM, no base64
75
+		result.SCRAM = true
76
+	case 60:
77
+		// e.g., #define PBKDF2_PRF_SCRAM_MD5_S64        63U
78
+		result.SaltB64 = true
79
+		result.SCRAM = true
80
+	default:
81
+		return result, ErrHashInvalid
82
+	}
83
+
84
+	switch hashCode {
85
+	case 3:
86
+		result.Hash, result.OutputSize = md5.New, (128 / 8)
87
+	case 4:
88
+		result.Hash, result.OutputSize = sha1.New, (160 / 8)
89
+	case 5:
90
+		result.Hash, result.OutputSize = sha256.New, (256 / 8)
91
+	case 6:
92
+		result.Hash, result.OutputSize = sha512.New, (512 / 8)
93
+	default:
94
+		return result, ErrHashInvalid
95
+	}
96
+
97
+	return result, nil
98
+}
99
+
100
+func checkAthemePBKDF2V2(hash, passphrase []byte) (err error) {
101
+	// crypto/pbkdf2v2, the default as of september 2020:
102
+	// "the format for pbkdf2v2 is $z$alg$iter$salt$digest
103
+	// where the z is literal,
104
+	// the alg is one from  https://github.com/atheme/atheme/blob/master/include/atheme/pbkdf2.h#L34-L52
105
+	// iter is the iteration count.
106
+	// if the alg ends in _S64 then the salt is base64-encoded, otherwise taken literally
107
+	// (an ASCII salt, inherited from the pbkdf2 module).
108
+	// if alg is a SCRAM one, then digest is actually serverkey$storedkey (see RFC 5802).
109
+	// digest, serverkey and storedkey are base64-encoded."
110
+	parts := bytes.Split(hash, []byte{'$'})
111
+	if len(parts) < 6 {
112
+		return ErrHashInvalid
113
+	}
114
+	algo, err := athemePBKDF2V2ParseAlgo(string(parts[2]))
115
+	if err != nil {
116
+		return err
117
+	}
118
+
119
+	iter, err := strconv.Atoi(string(parts[3]))
120
+	if err != nil {
121
+		return ErrHashInvalid
122
+	}
123
+
124
+	salt := parts[4]
125
+	if algo.SaltB64 {
126
+		salt, err = base64.StdEncoding.DecodeString(string(salt))
127
+		if err != nil {
128
+			return err
129
+		}
130
+	}
131
+
132
+	// if SCRAM, parts[5] is ServerKey; otherwise it's the actual PBKDF2 output
133
+	// either way, it's what we'll test against
134
+	expected, err := base64.StdEncoding.DecodeString(string(parts[5]))
135
+	if err != nil {
136
+		return err
137
+	}
138
+
139
+	var key []byte
140
+	if algo.SCRAM {
141
+		if len(parts) != 7 {
142
+			return ErrHashInvalid
143
+		}
144
+		stretch := pbkdf2.Key(passphrase, salt, iter, algo.OutputSize, algo.Hash)
145
+		mac := hmac.New(algo.Hash, stretch)
146
+		mac.Write(hmacServerKeyText)
147
+		key = mac.Sum(nil)
148
+	} else {
149
+		if len(parts) != 6 {
150
+			return ErrHashInvalid
151
+		}
152
+		key = pbkdf2.Key(passphrase, salt, iter, len(expected), algo.Hash)
153
+	}
154
+
155
+	if subtle.ConstantTimeCompare(key, expected) == 1 {
156
+		return nil
157
+	} else {
158
+		return ErrHashCheckFailed
159
+	}
160
+}
161
+
162
+func checkAthemePBKDF2(hash, passphrase []byte) (err error) {
163
+	// crypto/pbkdf2:
164
+	// "SHA2-512, 128000 iterations, 16-ASCII-character salt, hexadecimal encoding of digest,
165
+	// digest appended directly to salt, for a single string consisting of only 144 characters"
166
+	if len(hash) != 144 {
167
+		return ErrHashInvalid
168
+	}
169
+
170
+	salt := hash[:16]
171
+	digest := make([]byte, 64)
172
+	cnt, err := hex.Decode(digest, hash[16:])
173
+	if err != nil || cnt != 64 {
174
+		return ErrHashCheckFailed
175
+	}
176
+
177
+	key := pbkdf2.Key(passphrase, salt, 128000, 64, sha512.New)
178
+	if subtle.ConstantTimeCompare(key, digest) == 1 {
179
+		return nil
180
+	} else {
181
+		return ErrHashCheckFailed
182
+	}
183
+}

+ 72
- 0
irc/migrations/passwords_test.go View File

@@ -0,0 +1,72 @@
1
+// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2
+// released under the MIT license
3
+
4
+package migrations
5
+
6
+import (
7
+	"encoding/base64"
8
+	"testing"
9
+)
10
+
11
+func TestAthemePassphrases(t *testing.T) {
12
+	var err error
13
+
14
+	err = CheckAthemePassphrase([]byte("$1$hcspif$nCm4r3S14Me9ifsOPGuJT."), []byte("shivarampassphrase"))
15
+	if err != nil {
16
+		t.Errorf("failed to check passphrase: %v", err)
17
+	}
18
+
19
+	err = CheckAthemePassphrase([]byte("$1$hcspif$nCm4r3S14Me9ifsOPGuJT."), []byte("sh1varampassphrase"))
20
+	if err == nil {
21
+		t.Errorf("accepted invalid passphrase")
22
+	}
23
+
24
+	err = CheckAthemePassphrase([]byte("khMlbBBIFya2ihyN42abc3e768663e2c4fd0e0020e46292bf9fdf44e9a51d2a2e69509cb73b4b1bf9c1b6355a1fc9ea663fcd6da902287159494f15b905e5e651d6a60f2ec834598"), []byte("password"))
25
+	if err != nil {
26
+		t.Errorf("failed to check passphrase: %v", err)
27
+	}
28
+
29
+	err = CheckAthemePassphrase([]byte("khMlbBBIFya2ihyN42abc3e768663e2c4fd0e0020e46292bf9fdf44e9a51d2a2e69509cb73b4b1bf9c1b6355a1fc9ea663fcd6da902287159494f15b905e5e651d6a60f2ec834598"), []byte("passw0rd"))
30
+	if err == nil {
31
+		t.Errorf("accepted invalid passphrase")
32
+	}
33
+
34
+	err = CheckAthemePassphrase([]byte("$z$65$64000$1kz1I9YJPJ2gkJALbrpL2DoxRDhYPBOg60KNJMK/6do=$Cnfg6pYhBNrVXiaXYH46byrC+3HKet/XvYwvI1BvZbs=$m0hrT33gcF90n2TU3lm8tdm9V9XC4xEV13KsjuT38iY="), []byte("password"))
35
+	if err != nil {
36
+		t.Errorf("failed to check passphrase: %v", err)
37
+	}
38
+
39
+	err = CheckAthemePassphrase([]byte("$z$65$64000$1kz1I9YJPJ2gkJALbrpL2DoxRDhYPBOg60KNJMK/6do=$Cnfg6pYhBNrVXiaXYH46byrC+3HKet/XvYwvI1BvZbs=$m0hrT33gcF90n2TU3lm8tdm9V9XC4xEV13KsjuT38iY="), []byte("passw0rd"))
40
+	if err == nil {
41
+		t.Errorf("accepted invalid passphrase")
42
+	}
43
+}
44
+
45
+func TestOragonoLegacyPassphrase(t *testing.T) {
46
+	shivaramHash, err := base64.StdEncoding.DecodeString("ZPLKvCGipalUo9AlDIlMzAuY/ACWvM3yr1kh7k0/wa7lLlCwaPpe2ht9LNZZlZ9FPUWggUi7D4jyg2WnJDJhJDE0JDRsN0gwVmYvNHlyNjR1U212U2Q0YU9EVmRvWngwcXNGLkkyYVc4eUZISGxYaGE4SWVrRzRt")
47
+	if err != nil {
48
+		panic(err)
49
+	}
50
+	edHash, err := base64.StdEncoding.DecodeString("ZPLKvCGipalUo9AlDIlMzAuY/ACWvM3yr1kh7k0/+42q72mFnpDZWgjmqp1Zd77rEUO8ItYe4aGwWelUJDJhJDE0JHFqSGJ5NWVJbnJTdXBRT29pUmNUUWV5U2xmWjZETlRNcXlSMExUb2RmY3l1Skw2c3BTb3lh")
51
+	if err != nil {
52
+		panic(err)
53
+	}
54
+
55
+	err = CheckOragonoPassphraseV0(shivaramHash, []byte("shivarampassphrase"))
56
+	if err != nil {
57
+		t.Errorf("failed to check passphrase: %v", err)
58
+	}
59
+	err = CheckOragonoPassphraseV0(shivaramHash, []byte("edpassphrase"))
60
+	if err == nil {
61
+		t.Errorf("accepted invalid passphrase")
62
+	}
63
+
64
+	err = CheckOragonoPassphraseV0(edHash, []byte("edpassphrase"))
65
+	if err != nil {
66
+		t.Errorf("failed to check passphrase: %v", err)
67
+	}
68
+	err = CheckOragonoPassphraseV0(edHash, []byte("shivarampassphrase"))
69
+	if err == nil {
70
+		t.Errorf("accepted invalid passphrase")
71
+	}
72
+}

+ 10
- 1
oragono.go View File

@@ -96,6 +96,7 @@ func main() {
96 96
 Usage:
97 97
 	oragono initdb [--conf <filename>] [--quiet]
98 98
 	oragono upgradedb [--conf <filename>] [--quiet]
99
+	oragono importdb <database.json> [--conf <filename>] [--quiet]
99 100
 	oragono genpasswd [--conf <filename>] [--quiet]
100 101
 	oragono mkcerts [--conf <filename>] [--quiet]
101 102
 	oragono run [--conf <filename>] [--quiet] [--smoke]
@@ -155,7 +156,10 @@ Options:
155 156
 	}
156 157
 
157 158
 	if arguments["initdb"].(bool) {
158
-		irc.InitDB(config.Datastore.Path)
159
+		err = irc.InitDB(config.Datastore.Path)
160
+		if err != nil {
161
+			log.Fatal("Error while initializing db:", err.Error())
162
+		}
159 163
 		if !arguments["--quiet"].(bool) {
160 164
 			log.Println("database initialized: ", config.Datastore.Path)
161 165
 		}
@@ -167,6 +171,11 @@ Options:
167 171
 		if !arguments["--quiet"].(bool) {
168 172
 			log.Println("database upgraded: ", config.Datastore.Path)
169 173
 		}
174
+	} else if arguments["importdb"].(bool) {
175
+		err = irc.ImportDB(config, arguments["<database.json>"].(string))
176
+		if err != nil {
177
+			log.Fatal("Error while importing db:", err.Error())
178
+		}
170 179
 	} else if arguments["run"].(bool) {
171 180
 		if !arguments["--quiet"].(bool) {
172 181
 			logman.Info("server", fmt.Sprintf("%s starting", irc.Ver))

+ 7
- 0
vendor/github.com/GehirnInc/crypt/.travis.yml View File

@@ -0,0 +1,7 @@
1
+language: go
2
+go:
3
+  - 1.6.x
4
+  - 1.7.x
5
+  - master
6
+script:
7
+  - go test -v -race ./...

+ 8
- 0
vendor/github.com/GehirnInc/crypt/AUTHORS.md View File

@@ -0,0 +1,8 @@
1
+### Initial author
2
+
3
+[Jeramey Crawford](https://github.com/jeramey)
4
+
5
+### Other authors
6
+
7
+- [Jonas mg](https://github.com/kless)
8
+- [Kohei YOSHIDA](https://github.com/yosida95)

+ 26
- 0
vendor/github.com/GehirnInc/crypt/LICENSE View File

@@ -0,0 +1,26 @@
1
+Copyright (c) 2012, Jeramey Crawford <jeramey@antihe.ro>
2
+All rights reserved.
3
+
4
+Redistribution and use in source and binary forms, with or without
5
+modification, are permitted provided that the following conditions are
6
+met:
7
+
8
+  * Redistributions of source code must retain the above copyright
9
+    notice, this list of conditions and the following disclaimer.
10
+
11
+  * Redistributions in binary form must reproduce the above copyright
12
+    notice, this list of conditions and the following disclaimer in
13
+    the documentation and/or other materials provided with the
14
+    distribution.
15
+
16
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 61
- 0
vendor/github.com/GehirnInc/crypt/README.rst View File

@@ -0,0 +1,61 @@
1
+.. image:: https://travis-ci.org/GehirnInc/crypt.svg?branch=master
2
+    :target: https://travis-ci.org/GehirnInc/crypt
3
+
4
+crypt - A password hashing library for Go
5
+=========================================
6
+crypt provides pure golang implementations of UNIX's crypt(3).
7
+
8
+The goal of crypt is to bring a library of many common and popular password
9
+hashing algorithms to Go and to provide a simple and consistent interface to
10
+each of them. As every hashing method is implemented in pure Go, this library
11
+should be as portable as Go itself.
12
+
13
+All hashing methods come with a test suite which verifies their operation
14
+against itself as well as the output of other password hashing implementations
15
+to ensure compatibility with them.
16
+
17
+I hope you find this library to be useful and easy to use!
18
+
19
+Install
20
+-------
21
+
22
+To install crypt, use the *go get* command.
23
+
24
+.. code-block:: sh
25
+
26
+   go get github.com/GehirnInc/crypt
27
+
28
+
29
+Usage
30
+-----
31
+
32
+.. code-block:: go
33
+
34
+    package main
35
+
36
+    import (
37
+    	"fmt"
38
+
39
+    	"github.com/GehirnInc/crypt"
40
+    	_ "github.com/GehirnInc/crypt/sha256_crypt"
41
+    )
42
+
43
+    func main() {
44
+    	crypt := crypt.SHA256.New()
45
+    	ret, _ := crypt.Generate([]byte("secret"), []byte("$5$salt"))
46
+    	fmt.Println(ret)
47
+
48
+    	err := crypt.Verify(ret, []byte("secret"))
49
+    	fmt.Println(err)
50
+
51
+    	// Output:
52
+    	// $5$salt$kpa26zwgX83BPSR8d7w93OIXbFt/d3UOTZaAu5vsTM6
53
+    	// <nil>
54
+    }
55
+
56
+Documentation
57
+-------------
58
+
59
+The documentation is available on GoDoc_.
60
+
61
+.. _GoDoc: https://godoc.org/github.com/GehirnInc/crypt

+ 59
- 0
vendor/github.com/GehirnInc/crypt/common/base64.go View File

@@ -0,0 +1,59 @@
1
+// (C) Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>. All
2
+// rights reserved. Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+package common
6
+
7
+const (
8
+	alphabet = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
9
+)
10
+
11
+// Base64_24Bit is a variant of Base64 encoding, commonly used with password
12
+// hashing algorithms to encode the result of their checksum output.
13
+//
14
+// The algorithm operates on up to 3 bytes at a time, encoding the following
15
+// 6-bit sequences into up to 4 hash64 ASCII bytes.
16
+//
17
+//   1. Bottom 6 bits of the first byte
18
+//   2. Top 2 bits of the first byte, and bottom 4 bits of the second byte.
19
+//   3. Top 4 bits of the second byte, and bottom 2 bits of the third byte.
20
+//   4. Top 6 bits of the third byte.
21
+//
22
+// This encoding method does not emit padding bytes as Base64 does.
23
+func Base64_24Bit(src []byte) []byte {
24
+	if len(src) == 0 {
25
+		return []byte{} // TODO: return nil
26
+	}
27
+
28
+	dstlen := (len(src)*8 + 5) / 6
29
+	dst := make([]byte, dstlen)
30
+
31
+	di, si := 0, 0
32
+	n := len(src) / 3 * 3
33
+	for si < n {
34
+		val := uint(src[si+2])<<16 | uint(src[si+1])<<8 | uint(src[si])
35
+		dst[di+0] = alphabet[val&0x3f]
36
+		dst[di+1] = alphabet[val>>6&0x3f]
37
+		dst[di+2] = alphabet[val>>12&0x3f]
38
+		dst[di+3] = alphabet[val>>18]
39
+		di += 4
40
+		si += 3
41
+	}
42
+
43
+	rem := len(src) - si
44
+	if rem == 0 {
45
+		return dst
46
+	}
47
+
48
+	val := uint(src[si+0])
49
+	if rem == 2 {
50
+		val |= uint(src[si+1]) << 8
51
+	}
52
+
53
+	dst[di+0] = alphabet[val&0x3f]
54
+	dst[di+1] = alphabet[val>>6&0x3f]
55
+	if rem == 2 {
56
+		dst[di+2] = alphabet[val>>12]
57
+	}
58
+	return dst
59
+}

+ 10
- 0
vendor/github.com/GehirnInc/crypt/common/doc.go View File

@@ -0,0 +1,10 @@
1
+// (C) Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>. All
2
+// rights reserved. Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+// Package common contains routines used by multiple password hashing
6
+// algorithms.
7
+//
8
+// Generally, you will never import this package directly. Many of the
9
+// *_crypt packages will import this package if they require it.
10
+package common

+ 148
- 0
vendor/github.com/GehirnInc/crypt/common/salt.go View File

@@ -0,0 +1,148 @@
1
+// (C) Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>. All
2
+// rights reserved. Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+package common
6
+
7
+import (
8
+	"bytes"
9
+	"crypto/rand"
10
+	"errors"
11
+	"strconv"
12
+)
13
+
14
+var (
15
+	ErrSaltPrefix = errors.New("invalid magic prefix")
16
+	ErrSaltFormat = errors.New("invalid salt format")
17
+	ErrSaltRounds = errors.New("invalid rounds")
18
+)
19
+
20
+const (
21
+	roundsPrefix = "rounds="
22
+)
23
+
24
+// Salt represents a salt.
25
+type Salt struct {
26
+	MagicPrefix []byte
27
+
28
+	SaltLenMin int
29
+	SaltLenMax int
30
+
31
+	RoundsMin     int
32
+	RoundsMax     int
33
+	RoundsDefault int
34
+}
35
+
36
+// Generate generates a random salt of a given length.
37
+//
38
+// The length is set thus:
39
+//
40
+//   length > SaltLenMax: length = SaltLenMax
41
+//   length < SaltLenMin: length = SaltLenMin
42
+func (s *Salt) Generate(length int) []byte {
43
+	if length > s.SaltLenMax {
44
+		length = s.SaltLenMax
45
+	} else if length < s.SaltLenMin {
46
+		length = s.SaltLenMin
47
+	}
48
+
49
+	saltLen := (length * 6 / 8)
50
+	if (length*6)%8 != 0 {
51
+		saltLen += 1
52
+	}
53
+	salt := make([]byte, saltLen)
54
+	rand.Read(salt)
55
+
56
+	out := make([]byte, len(s.MagicPrefix)+length)
57
+	copy(out, s.MagicPrefix)
58
+	copy(out[len(s.MagicPrefix):], Base64_24Bit(salt))
59
+	return out
60
+}
61
+
62
+// GenerateWRounds creates a random salt with the random bytes being of the
63
+// length provided, and the rounds parameter set as specified.
64
+//
65
+// The parameters are set thus:
66
+//
67
+//   length > SaltLenMax: length = SaltLenMax
68
+//   length < SaltLenMin: length = SaltLenMin
69
+//
70
+//   rounds < 0: rounds = RoundsDefault
71
+//   rounds < RoundsMin: rounds = RoundsMin
72
+//   rounds > RoundsMax: rounds = RoundsMax
73
+//
74
+// If rounds is equal to RoundsDefault, then the "rounds=" part of the salt is
75
+// removed.
76
+func (s *Salt) GenerateWRounds(length, rounds int) []byte {
77
+	if length > s.SaltLenMax {
78
+		length = s.SaltLenMax
79
+	} else if length < s.SaltLenMin {
80
+		length = s.SaltLenMin
81
+	}
82
+	if rounds < 0 {
83
+		rounds = s.RoundsDefault
84
+	} else if rounds < s.RoundsMin {
85
+		rounds = s.RoundsMin
86
+	} else if rounds > s.RoundsMax {
87
+		rounds = s.RoundsMax
88
+	}
89
+
90
+	saltLen := (length * 6 / 8)
91
+	if (length*6)%8 != 0 {
92
+		saltLen += 1
93
+	}
94
+	salt := make([]byte, saltLen)
95
+	rand.Read(salt)
96
+
97
+	roundsText := ""
98
+	if rounds != s.RoundsDefault {
99
+		roundsText = roundsPrefix + strconv.Itoa(rounds) + "$"
100
+	}
101
+
102
+	out := make([]byte, len(s.MagicPrefix)+len(roundsText)+length)
103
+	copy(out, s.MagicPrefix)
104
+	copy(out[len(s.MagicPrefix):], []byte(roundsText))
105
+	copy(out[len(s.MagicPrefix)+len(roundsText):], Base64_24Bit(salt))
106
+	return out
107
+}
108
+
109
+func (s *Salt) Decode(raw []byte) (salt []byte, rounds int, isRoundsDef bool, rest []byte, err error) {
110
+	tokens := bytes.SplitN(raw, []byte{'$'}, 4)
111
+	if len(tokens) < 3 {
112
+		err = ErrSaltFormat
113
+		return
114
+	}
115
+	if !bytes.HasPrefix(raw, s.MagicPrefix) {
116
+		err = ErrSaltPrefix
117
+		return
118
+	}
119
+
120
+	if bytes.HasPrefix(tokens[2], []byte(roundsPrefix)) {
121
+		if len(tokens) < 4 {
122
+			err = ErrSaltFormat
123
+			return
124
+		}
125
+		salt = tokens[3]
126
+
127
+		rounds, err = strconv.Atoi(string(tokens[2][len(roundsPrefix):]))
128
+		if err != nil {
129
+			err = ErrSaltRounds
130
+			return
131
+		}
132
+		if rounds < s.RoundsMin {
133
+			rounds = s.RoundsMin
134
+		}
135
+		if rounds > s.RoundsMax {
136
+			rounds = s.RoundsMax
137
+		}
138
+		isRoundsDef = true
139
+	} else {
140
+		salt = tokens[2]
141
+		rounds = s.RoundsDefault
142
+	}
143
+	if len(salt) > s.SaltLenMax {
144
+		salt = salt[0:s.SaltLenMax]
145
+	}
146
+
147
+	return
148
+}

+ 121
- 0
vendor/github.com/GehirnInc/crypt/crypt.go View File

@@ -0,0 +1,121 @@
1
+// (C) Copyright 2013, Jonas mg. All rights reserved.
2
+// Use of this source code is governed by a BSD-style license
3
+// that can be found in the LICENSE file.
4
+
5
+// Package crypt provides interface for password crypt functions and collects
6
+// common constants.
7
+package crypt
8
+
9
+import (
10
+	"errors"
11
+	"strings"
12
+
13
+	"github.com/GehirnInc/crypt/common"
14
+)
15
+
16
+var ErrKeyMismatch = errors.New("hashed value is not the hash of the given password")
17
+
18
+// Crypter is the common interface implemented by all crypt functions.
19
+type Crypter interface {
20
+	// Generate performs the hashing algorithm, returning a full hash suitable
21
+	// for storage and later password verification.
22
+	//
23
+	// If the salt is empty, a randomly-generated salt will be generated with a
24
+	// length of SaltLenMax and number RoundsDefault of rounds.
25
+	//
26
+	// Any error only can be got when the salt argument is not empty.
27
+	Generate(key, salt []byte) (string, error)
28
+
29
+	// Verify compares a hashed key with its possible key equivalent.
30
+	// Returns nil on success, or an error on failure; if the hashed key is
31
+	// diffrent, the error is "ErrKeyMismatch".
32
+	Verify(hashedKey string, key []byte) error
33
+
34
+	// Cost returns the hashing cost (in rounds) used to create the given hashed
35
+	// key.
36
+	//
37
+	// When, in the future, the hashing cost of a key needs to be increased in
38
+	// order to adjust for greater computational power, this function allows one
39
+	// to establish which keys need to be updated.
40
+	//
41
+	// The algorithms based in MD5-crypt use a fixed value of rounds.
42
+	Cost(hashedKey string) (int, error)
43
+
44
+	// SetSalt sets a different salt. It is used to easily create derivated
45
+	// algorithms, i.e. "apr1_crypt" from "md5_crypt".
46
+	SetSalt(salt common.Salt)
47
+}
48
+
49
+// Crypt identifies a crypt function that is implemented in another package.
50
+type Crypt uint
51
+
52
+const (
53
+	APR1   Crypt = 1 + iota // import github.com/GehirnInc/crypt/apr1_crypt
54
+	MD5                     // import github.com/GehirnInc/crypt/md5_crypt
55
+	SHA256                  // import github.com/GehirnInc/crypt/sha256_crypt
56
+	SHA512                  // import github.com/GehirnInc/crypt/sha512_crypt
57
+	maxCrypt
58
+)
59
+
60
+var crypts = make([]func() Crypter, maxCrypt)
61
+
62
+// New returns new Crypter making the Crypt c.
63
+// New panics if the Crypt c is unavailable.
64
+func (c Crypt) New() Crypter {
65
+	if c > 0 && c < maxCrypt {
66
+		f := crypts[c]
67
+		if f != nil {
68
+			return f()
69
+		}
70
+	}
71
+	panic("crypt: requested crypt function is unavailable")
72
+}
73
+
74
+// Available reports whether the Crypt c is available.
75
+func (c Crypt) Available() bool {
76
+	return c > 0 && c < maxCrypt && crypts[c] != nil
77
+}
78
+
79
+var cryptPrefixes = make([]string, maxCrypt)
80
+
81
+// RegisterCrypt registers a function that returns a new instance of the given
82
+// crypt function. This is intended to be called from the init function in
83
+// packages that implement crypt functions.
84
+func RegisterCrypt(c Crypt, f func() Crypter, prefix string) {
85
+	if c >= maxCrypt {
86
+		panic("crypt: RegisterHash of unknown crypt function")
87
+	}
88
+	crypts[c] = f
89
+	cryptPrefixes[c] = prefix
90
+}
91
+
92
+// New returns a new crypter.
93
+func New(c Crypt) Crypter {
94
+	return c.New()
95
+}
96
+
97
+// IsHashSupported returns true if hashedKey has a supported prefix.
98
+// NewFromHash will not panic for this hashedKey
99
+func IsHashSupported(hashedKey string) bool {
100
+	for i := range cryptPrefixes {
101
+		prefix := cryptPrefixes[i]
102
+		if crypts[i] != nil && strings.HasPrefix(hashedKey, prefix) {
103
+			return true
104
+		}
105
+	}
106
+
107
+	return false
108
+}
109
+
110
+// NewFromHash returns a new Crypter using the prefix in the given hashed key.
111
+func NewFromHash(hashedKey string) Crypter {
112
+	for i := range cryptPrefixes {
113
+		prefix := cryptPrefixes[i]
114
+		if crypts[i] != nil && strings.HasPrefix(hashedKey, prefix) {
115
+			crypt := Crypt(uint(i))
116
+			return crypt.New()
117
+		}
118
+	}
119
+
120
+	panic("crypt: unknown crypt function")
121
+}

+ 41
- 0
vendor/github.com/GehirnInc/crypt/internal/utils.go View File

@@ -0,0 +1,41 @@
1
+// Copyright (c) 2015 Kohei YOSHIDA. All rights reserved.
2
+// This software is licensed under the 3-Clause BSD License
3
+// that can be found in LICENSE file.
4
+package internal
5
+
6
+const (
7
+	cleanBytesLen = 64
8
+)
9
+
10
+var (
11
+	cleanBytes = make([]byte, cleanBytesLen)
12
+)
13
+
14
+func CleanSensitiveData(b []byte) {
15
+	l := len(b)
16
+
17
+	for ; l > cleanBytesLen; l -= cleanBytesLen {
18
+		copy(b[l-cleanBytesLen:l], cleanBytes)
19
+	}
20
+
21
+	if l > 0 {
22
+		copy(b[0:l], cleanBytes[0:l])
23
+	}
24
+}
25
+
26
+func RepeatByteSequence(input []byte, length int) []byte {
27
+	var (
28
+		sequence = make([]byte, length)
29
+		unit     = len(input)
30
+	)
31
+
32
+	j := length / unit * unit
33
+	for i := 0; i < j; i += unit {
34
+		copy(sequence[i:length], input)
35
+	}
36
+	if j < length {
37
+		copy(sequence[j:length], input[0:length-j])
38
+	}
39
+
40
+	return sequence
41
+}

+ 143
- 0
vendor/github.com/GehirnInc/crypt/md5_crypt/md5_crypt.go View File

@@ -0,0 +1,143 @@
1
+// (C) Copyright 2012, Jeramey Crawford <jeramey@antihe.ro>. All
2
+// rights reserved. Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+// Package md5_crypt implements the standard Unix MD5-crypt algorithm created by
6
+// Poul-Henning Kamp for FreeBSD.
7
+package md5_crypt
8
+
9
+import (
10
+	"bytes"
11
+	"crypto/md5"
12
+	"crypto/subtle"
13
+
14
+	"github.com/GehirnInc/crypt"
15
+	"github.com/GehirnInc/crypt/common"
16
+	"github.com/GehirnInc/crypt/internal"
17
+)
18
+
19
+func init() {
20
+	crypt.RegisterCrypt(crypt.MD5, New, MagicPrefix)
21
+}
22
+
23
+// NOTE: Cisco IOS only allows salts of length 4.
24
+
25
+const (
26
+	MagicPrefix   = "$1$"
27
+	SaltLenMin    = 1 // Real minimum is 0, but that isn't useful.
28
+	SaltLenMax    = 8
29
+	RoundsDefault = 1000
30
+)
31
+
32
+type crypter struct{ Salt common.Salt }
33
+
34
+// New returns a new crypt.Crypter computing the MD5-crypt password hashing.
35
+func New() crypt.Crypter {
36
+	return &crypter{
37
+		common.Salt{
38
+			MagicPrefix:   []byte(MagicPrefix),
39
+			SaltLenMin:    SaltLenMin,
40
+			SaltLenMax:    SaltLenMax,
41
+			RoundsDefault: RoundsDefault,
42
+		},
43
+	}
44
+}
45
+
46
+func (c *crypter) Generate(key, salt []byte) (result string, err error) {
47
+	if len(salt) == 0 {
48
+		salt = c.Salt.Generate(SaltLenMax)
49
+	}
50
+	salt, _, _, _, err = c.Salt.Decode(salt)
51
+	if err != nil {
52
+		return
53
+	}
54
+
55
+	keyLen := len(key)
56
+	h := md5.New()
57
+
58
+	// Compute sumB
59
+	h.Write(key)
60
+	h.Write(salt)
61
+	h.Write(key)
62
+	sumB := h.Sum(nil)
63
+
64
+	// Compute sumA
65
+	h.Reset()
66
+	h.Write(key)
67
+	h.Write(c.Salt.MagicPrefix)
68
+	h.Write(salt)
69
+	h.Write(internal.RepeatByteSequence(sumB, keyLen))
70
+	// The original implementation now does something weird:
71
+	//   For every 1 bit in the key, the first 0 is added to the buffer
72
+	//   For every 0 bit, the first character of the key
73
+	// This does not seem to be what was intended but we have to follow this to
74
+	// be compatible.
75
+	for i := keyLen; i > 0; i >>= 1 {
76
+		if i%2 == 0 {
77
+			h.Write(key[0:1])
78
+		} else {
79
+			h.Write([]byte{0})
80
+		}
81
+	}
82
+	sumA := h.Sum(nil)
83
+	internal.CleanSensitiveData(sumB)
84
+
85
+	// In fear of password crackers here comes a quite long loop which just
86
+	// processes the output of the previous round again.
87
+	// We cannot ignore this here.
88
+	for i := 0; i < RoundsDefault; i++ {
89
+		h.Reset()
90
+
91
+		// Add key or last result.
92
+		if i%2 != 0 {
93
+			h.Write(key)
94
+		} else {
95
+			h.Write(sumA)
96
+		}
97
+		// Add salt for numbers not divisible by 3.
98
+		if i%3 != 0 {
99
+			h.Write(salt)
100
+		}
101
+		// Add key for numbers not divisible by 7.
102
+		if i%7 != 0 {
103
+			h.Write(key)
104
+		}
105
+		// Add key or last result.
106
+		if i&1 != 0 {
107
+			h.Write(sumA)
108
+		} else {
109
+			h.Write(key)
110
+		}
111
+		copy(sumA, h.Sum(nil))
112
+	}
113
+
114
+	buf := bytes.Buffer{}
115
+	buf.Grow(len(c.Salt.MagicPrefix) + len(salt) + 1 + 22)
116
+	buf.Write(c.Salt.MagicPrefix)
117
+	buf.Write(salt)
118
+	buf.WriteByte('$')
119
+	buf.Write(common.Base64_24Bit([]byte{
120
+		sumA[12], sumA[6], sumA[0],
121
+		sumA[13], sumA[7], sumA[1],
122
+		sumA[14], sumA[8], sumA[2],
123
+		sumA[15], sumA[9], sumA[3],
124
+		sumA[5], sumA[10], sumA[4],
125
+		sumA[11],
126
+	}))
127
+	return buf.String(), nil
128
+}
129
+
130
+func (c *crypter) Verify(hashedKey string, key []byte) error {
131
+	newHash, err := c.Generate(key, []byte(hashedKey))
132
+	if err != nil {
133
+		return err
134
+	}
135
+	if subtle.ConstantTimeCompare([]byte(newHash), []byte(hashedKey)) != 1 {
136
+		return crypt.ErrKeyMismatch
137
+	}
138
+	return nil
139
+}
140
+
141
+func (c *crypter) Cost(hashedKey string) (int, error) { return RoundsDefault, nil }
142
+
143
+func (c *crypter) SetSalt(salt common.Salt) { c.Salt = salt }

+ 77
- 0
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go View File

@@ -0,0 +1,77 @@
1
+// Copyright 2012 The Go Authors. All rights reserved.
2
+// Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+/*
6
+Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC
7
+2898 / PKCS #5 v2.0.
8
+
9
+A key derivation function is useful when encrypting data based on a password
10
+or any other not-fully-random data. It uses a pseudorandom function to derive
11
+a secure encryption key based on the password.
12
+
13
+While v2.0 of the standard defines only one pseudorandom function to use,
14
+HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved
15
+Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
16
+choose, you can pass the `New` functions from the different SHA packages to
17
+pbkdf2.Key.
18
+*/
19
+package pbkdf2 // import "golang.org/x/crypto/pbkdf2"
20
+
21
+import (
22
+	"crypto/hmac"
23
+	"hash"
24
+)
25
+
26
+// Key derives a key from the password, salt and iteration count, returning a
27
+// []byte of length keylen that can be used as cryptographic key. The key is
28
+// derived based on the method described as PBKDF2 with the HMAC variant using
29
+// the supplied hash function.
30
+//
31
+// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
32
+// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
33
+// doing:
34
+//
35
+// 	dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
36
+//
37
+// Remember to get a good random salt. At least 8 bytes is recommended by the
38
+// RFC.
39
+//
40
+// Using a higher iteration count will increase the cost of an exhaustive
41
+// search but will also make derivation proportionally slower.
42
+func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
43
+	prf := hmac.New(h, password)
44
+	hashLen := prf.Size()
45
+	numBlocks := (keyLen + hashLen - 1) / hashLen
46
+
47
+	var buf [4]byte
48
+	dk := make([]byte, 0, numBlocks*hashLen)
49
+	U := make([]byte, hashLen)
50
+	for block := 1; block <= numBlocks; block++ {
51
+		// N.B.: || means concatenation, ^ means XOR
52
+		// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
53
+		// U_1 = PRF(password, salt || uint(i))
54
+		prf.Reset()
55
+		prf.Write(salt)
56
+		buf[0] = byte(block >> 24)
57
+		buf[1] = byte(block >> 16)
58
+		buf[2] = byte(block >> 8)
59
+		buf[3] = byte(block)
60
+		prf.Write(buf[:4])
61
+		dk = prf.Sum(dk)
62
+		T := dk[len(dk)-hashLen:]
63
+		copy(U, T)
64
+
65
+		// U_n = PRF(password, U_(n-1))
66
+		for n := 2; n <= iter; n++ {
67
+			prf.Reset()
68
+			prf.Write(U)
69
+			U = U[:0]
70
+			U = prf.Sum(U)
71
+			for x := range U {
72
+				T[x] ^= U[x]
73
+			}
74
+		}
75
+	}
76
+	return dk[:keyLen]
77
+}

+ 7
- 0
vendor/modules.txt View File

@@ -1,6 +1,12 @@
1 1
 # code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48
2 2
 ## explicit
3 3
 code.cloudfoundry.org/bytefmt
4
+# github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
5
+## explicit
6
+github.com/GehirnInc/crypt
7
+github.com/GehirnInc/crypt/common
8
+github.com/GehirnInc/crypt/internal
9
+github.com/GehirnInc/crypt/md5_crypt
4 10
 # github.com/dgrijalva/jwt-go v3.2.0+incompatible
5 11
 ## explicit
6 12
 github.com/dgrijalva/jwt-go
@@ -56,6 +62,7 @@ github.com/toorop/go-dkim
56 62
 ## explicit
57 63
 golang.org/x/crypto/bcrypt
58 64
 golang.org/x/crypto/blowfish
65
+golang.org/x/crypto/pbkdf2
59 66
 golang.org/x/crypto/sha3
60 67
 golang.org/x/crypto/ssh/terminal
61 68
 # golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f

Loading…
Cancel
Save