This repository was archived by the owner on Dec 12, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 511
Expand file tree
/
Copy pathscram_credentials.go
More file actions
165 lines (131 loc) · 5.67 KB
/
Copy pathscram_credentials.go
File metadata and controls
165 lines (131 loc) · 5.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package scramcredentials
import (
"crypto/hmac"
"crypto/sha1" //nolint
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"hash"
"github.com/xdg/stringprep"
)
const (
RFC5802MandatedSaltSize = 4
clientKeyInput = "Client Key" // specified in RFC 5802
serverKeyInput = "Server Key" // specified in RFC 5802
// using the default MongoDB values for the number of iterations depending on mechanism
DefaultScramSha1Iterations = 10000
DefaultScramSha256Iterations = 15000
)
type ScramCreds struct {
IterationCount int `json:"iterationCount"`
Salt string `json:"salt"`
ServerKey string `json:"serverKey"`
StoredKey string `json:"storedKey"`
}
func ComputeScramSha256Creds(password string, salt []byte) (ScramCreds, error) {
base64EncodedSalt := base64.StdEncoding.EncodeToString(salt)
return computeScramCredentials(sha256.New, DefaultScramSha256Iterations, base64EncodedSalt, password)
}
func ComputeScramSha1Creds(username, password string, salt []byte) (ScramCreds, error) {
base64EncodedSalt := base64.StdEncoding.EncodeToString(salt)
password = sha256Hex(username + ":mongo:" + password)
return computeScramCredentials(sha1.New, DefaultScramSha1Iterations, base64EncodedSalt, password)
}
func sha256Hex(s string) string {
h := sha256.New() // nolint
h.Write([]byte(s)) //nolint
return hex.EncodeToString(h.Sum(nil))
}
func generateSaltedPassword(hashConstructor func() hash.Hash, password string, salt []byte, iterationCount int) ([]byte, error) {
preparedPassword, err := stringprep.SASLprep.Prepare(password)
if err != nil {
return nil, fmt.Errorf("could not SASLprep password: %s", err)
}
result, err := hmacIteration(hashConstructor, []byte(preparedPassword), salt, iterationCount)
if err != nil {
return nil, fmt.Errorf("could not run hmacIteration: %s", err)
}
return result, nil
}
func hmacIteration(hashConstructor func() hash.Hash, input, salt []byte, iterationCount int) ([]byte, error) {
hashSize := hashConstructor().Size()
// incorrect salt size will pass validation, but the credentials will be invalid. i.e. it will not
// be possible to auth with the password provided to create the credentials.
if len(salt) != hashSize-RFC5802MandatedSaltSize {
return nil, fmt.Errorf("salt should have a size of %d bytes, but instead has a size of %d bytes", hashSize-RFC5802MandatedSaltSize, len(salt))
}
startKey := append(salt, 0, 0, 0, 1)
result := make([]byte, hashSize)
hmacHash := hmac.New(hashConstructor, input)
if _, err := hmacHash.Write(startKey); err != nil {
return nil, fmt.Errorf("error running hmacHash: %s", err)
}
intermediateDigest := hmacHash.Sum(nil)
copy(result, intermediateDigest)
for i := 1; i < iterationCount; i++ {
hmacHash.Reset()
if _, err := hmacHash.Write(intermediateDigest); err != nil {
return nil, fmt.Errorf("error running hmacHash: %s", err)
}
intermediateDigest = hmacHash.Sum(nil)
for i := 0; i < len(intermediateDigest); i++ {
result[i] ^= intermediateDigest[i]
}
}
return result, nil
}
func generateClientOrServerKey(hashConstructor func() hash.Hash, saltedPassword []byte, input string) ([]byte, error) {
hmacHash := hmac.New(hashConstructor, saltedPassword)
if _, err := hmacHash.Write([]byte(input)); err != nil {
return nil, fmt.Errorf("error running hmacHash: %s", err)
}
return hmacHash.Sum(nil), nil
}
func generateStoredKey(hashConstructor func() hash.Hash, clientKey []byte) ([]byte, error) {
h := hashConstructor()
if _, err := h.Write(clientKey); err != nil {
return nil, fmt.Errorf("error hashing: %s", err)
}
return h.Sum(nil), nil
}
func generateSecrets(hashConstructor func() hash.Hash, password string, salt []byte, iterationCount int) (storedKey, serverKey []byte, err error) {
saltedPassword, err := generateSaltedPassword(hashConstructor, password, salt, iterationCount)
if err != nil {
return nil, nil, fmt.Errorf("error generating salted password: %s", err)
}
clientKey, err := generateClientOrServerKey(hashConstructor, saltedPassword, clientKeyInput)
if err != nil {
return nil, nil, fmt.Errorf("error generating client key: %s", err)
}
storedKey, err = generateStoredKey(hashConstructor, clientKey)
if err != nil {
return nil, nil, fmt.Errorf("error generating stored key: %s", err)
}
serverKey, err = generateClientOrServerKey(hashConstructor, saltedPassword, serverKeyInput)
if err != nil {
return nil, nil, fmt.Errorf("error generating server key: %s", err)
}
return storedKey, serverKey, err
}
func generateB64EncodedSecrets(hashConstructor func() hash.Hash, password, b64EncodedSalt string, iterationCount int) (storedKey, serverKey string, err error) {
salt, err := base64.StdEncoding.DecodeString(b64EncodedSalt)
if err != nil {
return "", "", fmt.Errorf("error decoding salt: %s", err)
}
unencodedStoredKey, unencodedServerKey, err := generateSecrets(hashConstructor, password, salt, iterationCount)
if err != nil {
return "", "", fmt.Errorf("error generating secrets: %s", err)
}
storedKey = base64.StdEncoding.EncodeToString(unencodedStoredKey)
serverKey = base64.StdEncoding.EncodeToString(unencodedServerKey)
return storedKey, serverKey, nil
}
// password should be encrypted in the case of SCRAM-SHA-1 and unencrypted in the case of SCRAM-SHA-256
func computeScramCredentials(hashConstructor func() hash.Hash, iterationCount int, base64EncodedSalt string, password string) (ScramCreds, error) {
storedKey, serverKey, err := generateB64EncodedSecrets(hashConstructor, password, base64EncodedSalt, iterationCount)
if err != nil {
return ScramCreds{}, fmt.Errorf("error generating SCRAM-SHA keys: %s", err)
}
return ScramCreds{IterationCount: iterationCount, Salt: base64EncodedSalt, StoredKey: storedKey, ServerKey: serverKey}, nil
}