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 )
32
33
33
34
result .salt = base64.encode (salt)
34
35
result .iterations = iterations
35
36
result .storedKey = base64.encode ($% storedKey)
36
37
result .serverKey = base64.encode ($% serverKey)
37
38
39
+ proc initUserData * (password: string , iterations = 4096 ): UserData =
40
+ initUserData (Sha256Digest , password, iterations)
41
+
38
42
proc initUserData * (salt: string , iterations: int , serverKey, storedKey: string ): UserData =
39
43
result .salt = salt
40
44
result .iterations = iterations
@@ -45,14 +49,19 @@ proc newScramServer*[T](): ScramServer[T] {.deprecated: "use `new ScramServer[T]
45
49
new ScramServer [T]
46
50
47
51
proc handleClientFirstMessage * [T](s: ScramServer [T],clientFirstMessage: string ): string =
52
+ let parts = clientFirstMessage.split (',' , 2 )
48
53
var matches: array [3 , string ]
49
- if not match (clientFirstMessage, CLIENT_FIRST_MESSAGE , matches):
54
+ if not match (clientFirstMessage, CLIENT_FIRST_MESSAGE , matches) or not parts.len == 3 :
50
55
s.state = ENDED
51
56
return
52
- s.clientFirstMessageBare = matches[ 0 ]
53
- s.serverNonce = matches[ 2 ] & makeNonce ()
57
+ s.clientFirstMessageBare = parts[ 2 ]
58
+
54
59
s.state = FIRST_CLIENT_MESSAGE_HANDLED
55
- matches[1 ] # username
60
+ for kv in s.clientFirstMessageBare.split (',' ):
61
+ if kv[0 .. 1 ] == " n=" :
62
+ result = kv[2 ..^ 1 ]
63
+ elif kv[0 .. 1 ] == " r=" :
64
+ s.serverNonce = kv[2 ..^ 1 ] & makeNonce ()
56
65
57
66
proc prepareFirstMessage * (s: ScramServer , userData: UserData ): string =
58
67
s.state = FIRST_PREPARED
@@ -65,10 +74,16 @@ proc prepareFinalMessage*[T](s: ScramServer[T], clientFinalMessage: string): str
65
74
if not match (clientFinalMessage, CLIENT_FINAL_MESSAGE , matches):
66
75
s.state = ENDED
67
76
return
68
- let
69
- clientFinalMessageWithoutProof = matches[0 ]
70
- nonce = matches[2 ]
71
- proof = matches[3 ]
77
+ var clientFinalMessageWithoutProof, nonce, proof: string
78
+ for kv in clientFinalMessage.split (',' ):
79
+ if kv[0 .. 1 ] == " p=" :
80
+ proof = kv[2 ..^ 1 ]
81
+ else :
82
+ if clientFinalMessageWithoutProof.len > 0 :
83
+ clientFinalMessageWithoutProof.add (',' )
84
+ clientFinalMessageWithoutProof.add (kv)
85
+ if kv[0 .. 1 ] == " r=" :
86
+ nonce = kv[2 ..^ 1 ]
72
87
73
88
if nonce != s.serverNonce:
74
89
s.state = ENDED
@@ -80,19 +95,21 @@ proc prepareFinalMessage*[T](s: ScramServer[T], clientFinalMessage: string): str
80
95
clientSignature = HMAC [T](storedKey, authMessage)
81
96
serverSignature = HMAC [T](decode (s.userData.serverKey), authMessage)
82
97
decodedProof = base64.decode (proof)
83
- var clientKey = $ clientSignature
84
- clientKey ^= decodedProof
98
+ clientKey = custom_xor ( clientSignature, decodedProof)
99
+ let resultKey = HASH [T](clientKey).raw_str
85
100
86
- let resultKey = $ HASH [T](clientKey)
87
- if resultKey != storedKey:
101
+ # SECURITY: constant time HMAC check
102
+ if not constantTimeEqual (resultKey, storedKey):
103
+ let k1 = base64.encode (resultKey)
104
+ let k2 = base64.encode (storedKey)
88
105
return
89
106
90
107
s.isSuccessful = true
91
108
s.state = ENDED
92
109
when NimMajor >= 1 and (NimMinor >= 1 or NimPatch >= 2 ):
93
- " v=" & base64.encode (serverSignature)
110
+ result = " v=" & base64.encode (serverSignature)
94
111
else :
95
- " v=" & base64.encode (serverSignature, newLine= " " )
112
+ result = " v=" & base64.encode (serverSignature, newLine= " " )
96
113
97
114
98
115
proc isSuccessful * (s: ScramServer ): bool =
0 commit comments