1
+ import strformat, strutils
1
2
import base64, pegs, strutils, hmac, nimSHA2, private/ [utils,types]
2
3
3
4
type
4
5
ScramServer * [T] = ref object of RootObj
5
- serverNonce: string
6
+ serverNonce* : string
6
7
clientFirstMessageBare: string
7
8
serverFirstMessage: string
8
- state: ScramState
9
+ state* : ScramState
9
10
isSuccessful: bool
10
11
userData: UserData
11
12
19
20
CLIENT_FIRST_MESSAGE = peg " ^([pny]'='?([^,]*)','([^,]*)','){('m='([^,]*)',')?'n='{[^,]*}',r='{[^,]*}(','(.*))*}$"
20
21
CLIENT_FINAL_MESSAGE = peg " {'c='{[^,]*}',r='{[^,]*}}',p='{.*}$"
21
22
22
- proc initUserData * ( password: string , iterations = 4096 ): UserData =
23
+ proc initUserData * [T](typ: typedesc [T], password: string , iterations = 4096 ): UserData =
23
24
var iterations = iterations
24
25
if password.len == 0 :
25
26
iterations = 1
26
27
let
27
28
salt = makeNonce ()[0 .. 24 ]
28
- saltedPassword = hi [SHA256Digest ](password, salt, iterations)
29
- clientKey = HMAC [SHA256Digest ]($% saltedPassword, CLIENT_KEY )
30
- storedKey = HASH [SHA256Digest ]($% clientKey)
31
- serverKey = HMAC [SHA256Digest ]($% saltedPassword, SERVER_KEY )
29
+ saltedPassword = hi [T](password, salt, iterations)
30
+ clientKey = HMAC [T]($% saltedPassword, CLIENT_KEY )
31
+ storedKey = HASH [T]($% clientKey)
32
+ serverKey = HMAC [T]($% saltedPassword, SERVER_KEY )
33
+
34
+ # echo &"server password {password}"
35
+ # echo &"server salt {salt}"
36
+ # echo &"server iterations {iterations}"
37
+ # echo &"server saltedPassword {base64.encode(saltedPassword)}"
38
+ # echo &"server clientKey {base64.encode(clientKey)}"
39
+ # echo &"server serverKey {base64.encode(serverKey)}"
40
+ # echo &"server storedKey {base64.encode(storedKey)}"
32
41
33
42
result .salt = base64.encode (salt)
34
43
result .iterations = iterations
35
44
result .storedKey = base64.encode ($% storedKey)
36
45
result .serverKey = base64.encode ($% serverKey)
37
46
47
+ proc initUserData * (password: string , iterations = 4096 ): UserData =
48
+ initUserData (Sha256Digest , password, iterations)
49
+
38
50
proc initUserData * (salt: string , iterations: int , serverKey, storedKey: string ): UserData =
39
51
result .salt = salt
40
52
result .iterations = iterations
@@ -45,33 +57,61 @@ proc newScramServer*[T](): ScramServer[T] {.deprecated: "use `new ScramServer[T]
45
57
new ScramServer [T]
46
58
47
59
proc handleClientFirstMessage * [T](s: ScramServer [T],clientFirstMessage: string ): string =
60
+ let parts = clientFirstMessage.split (',' , 2 )
48
61
var matches: array [3 , string ]
49
- if not match (clientFirstMessage, CLIENT_FIRST_MESSAGE , matches):
62
+ # echo &"client first message {clientFirstMessage}"
63
+ if not match (clientFirstMessage, CLIENT_FIRST_MESSAGE , matches) or not parts.len == 3 :
50
64
s.state = ENDED
51
65
return
52
- s.clientFirstMessageBare = matches[0 ]
53
- s.serverNonce = matches[2 ] & makeNonce ()
66
+ # echo &"client first message matches {matches}"
67
+ s.clientFirstMessageBare = parts[2 ]
68
+ # Disabled code until this is resolved
69
+ # <https://github.com/nim-lang/Nim/issues/19104>
70
+ # s.serverNonce = matches[2] & makeNonce()
71
+ # echo &"s.serverNonce = {s.serverNonce}"
72
+ # echo &"username = {matches[1]}"
73
+ # s.state = FIRST_CLIENT_MESSAGE_HANDLED
74
+ # matches[1] # username
75
+
54
76
s.state = FIRST_CLIENT_MESSAGE_HANDLED
55
- matches[1 ] # username
77
+ for kv in s.clientFirstMessageBare.split (',' ):
78
+ if kv[0 .. 1 ] == " n=" :
79
+ result = kv[2 ..^ 1 ]
80
+ elif kv[0 .. 1 ] == " r=" :
81
+ s.serverNonce = kv[2 ..^ 1 ] & makeNonce ()
56
82
57
83
proc prepareFirstMessage * (s: ScramServer , userData: UserData ): string =
58
84
s.state = FIRST_PREPARED
59
85
s.userData = userData
60
86
s.serverFirstMessage = " r=$#,s=$#,i=$#" % [s.serverNonce, userData.salt, $ userData.iterations]
87
+ # echo &"server first message: {s.serverFirstMessage}"
61
88
s.serverFirstMessage
62
89
63
90
proc prepareFinalMessage * [T](s: ScramServer [T], clientFinalMessage: string ): string =
64
91
var matches: array [4 , string ]
92
+ # echo &"client final message {clientFinalMessage}"
65
93
if not match (clientFinalMessage, CLIENT_FINAL_MESSAGE , matches):
66
94
s.state = ENDED
67
95
return
68
- let
69
- clientFinalMessageWithoutProof = matches[0 ]
70
- nonce = matches[2 ]
71
- proof = matches[3 ]
96
+ # echo &"client final message matches {matches}"
97
+ # let
98
+ # clientFinalMessageWithoutProof = matches[0]
99
+ # nonce = matches[2]
100
+ # proof = matches[3]
101
+ var clientFinalMessageWithoutProof, nonce, proof: string
102
+ for kv in clientFinalMessage.split (',' ):
103
+ if kv[0 .. 1 ] == " p=" :
104
+ proof = kv[2 ..^ 1 ]
105
+ else :
106
+ if clientFinalMessageWithoutProof.len > 0 :
107
+ clientFinalMessageWithoutProof.add (',' )
108
+ clientFinalMessageWithoutProof.add (kv)
109
+ if kv[0 .. 1 ] == " r=" :
110
+ nonce = kv[2 ..^ 1 ]
72
111
73
112
if nonce != s.serverNonce:
74
113
s.state = ENDED
114
+ # echo &"nonce mismatch {nonce} != {s.serverNonce}"
75
115
return
76
116
77
117
let
@@ -80,19 +120,34 @@ proc prepareFinalMessage*[T](s: ScramServer[T], clientFinalMessage: string): str
80
120
clientSignature = HMAC [T](storedKey, authMessage)
81
121
serverSignature = HMAC [T](decode (s.userData.serverKey), authMessage)
82
122
decodedProof = base64.decode (proof)
83
- var clientKey = $ clientSignature
84
- clientKey ^= decodedProof
85
-
86
- let resultKey = $ HASH [T](clientKey)
87
- if resultKey != storedKey:
123
+ clientKey = custom_xor (clientSignature, decodedProof)
124
+ # var clientKey = $clientSignature
125
+ # clientKey ^= decodedProof
126
+ let resultKey = HASH [T](clientKey).raw_str
127
+ # echo &"server storedKey {base64.encode(storedKey)}"
128
+ # echo &"server resultKey {base64.encode(resultKey)}"
129
+ # echo &"server authMessage.1 {s.clientFirstMessageBare}"
130
+ # echo &"server authMessage.2 {s.serverFirstMessage}"
131
+ # echo &"server authMessage.3 {clientFinalMessageWithoutProof}"
132
+ # echo &"server authMessage {authMessage}"
133
+ # echo &"server clientSignature {base64.encode(clientSignature)}"
134
+ # echo &"server clientKey {base64.encode(clientKey)} .len = {clientKey.len} {$typeof(clientSignature)}"
135
+ # echo &"server decodedProof {base64.encode(decodedProof)} .len = {decodedProof.len}"
136
+
137
+ # SECURITY: constant time HMAC check
138
+ if not constantTimeEqual (resultKey, storedKey):
139
+ let k1 = base64.encode (resultKey)
140
+ let k2 = base64.encode (storedKey)
141
+ # echo &"key mismatch {k1} != {k2}"
88
142
return
89
143
90
144
s.isSuccessful = true
91
145
s.state = ENDED
92
146
when NimMajor >= 1 and (NimMinor >= 1 or NimPatch >= 2 ):
93
- " v=" & base64.encode (serverSignature)
147
+ result = " v=" & base64.encode (serverSignature)
94
148
else :
95
- " v=" & base64.encode (serverSignature, newLine= " " )
149
+ result = " v=" & base64.encode (serverSignature, newLine= " " )
150
+ # echo &"server final message: {result}"
96
151
97
152
98
153
proc isSuccessful * (s: ScramServer ): bool =
0 commit comments