1
1
import base64, pegs, random, strutils, hmac, nimSHA2, securehash, md5, private/ [utils,types]
2
2
3
- export MD5Digest , Sha256Digest , Sha512Digest
3
+ export MD5Digest , SHA1Digest , SHA256Digest , SHA512Digest
4
4
5
5
type
6
6
ScramClient [T] = ref object of RootObj
7
7
clientNonce: string
8
- clientFirstBareMessage : string
8
+ clientFirstMessageBare : string
9
9
state: ScramState
10
10
isSuccessful: bool
11
11
serverSignature: T
12
12
13
- const
14
- GS2_HEADER = " n,,"
15
- INT_1 = " \ x0\ x0\ x0\ x1"
16
- CLIENT_KEY = " Client Key"
17
- SERVER_KEY = " Server Key"
18
-
19
13
let
20
14
SERVER_FIRST_MESSAGE = peg " 'r='{[^,]*}',s='{[^,]*}',i='{\ d+}$"
21
15
SERVER_FINAL_MESSAGE = peg " 'v='{[^,]*}$"
22
16
23
17
24
- proc HMAC [T](password, salt: string ): T =
25
- when T is MD5Digest :
26
- result = hmac_md5 (password, salt)
27
- elif T is Sha1Digest :
28
- result = Sha1Digest (hmac_sha1 (password, salt))
29
- elif T is Sha256Digest :
30
- result = hmac_sha256 (password, salt)
31
- elif T is Sha512Digest :
32
- result = hmac_sha512 (password, salt)
33
-
34
- proc HASH [T](s: string ): T =
35
- when T is MD5Digest :
36
- result = hash_md5 (s)
37
- elif T is Sha1Digest :
38
- result = Sha1Digest (hash_sha1 (s))
39
- elif T is Sha256Digest :
40
- result = hash_sha256 (s)
41
- elif T is Sha512Digest :
42
- result = hash_sha512 (s)
43
-
44
-
45
- proc hi [T](s: ScramClient [T], password, salt: string , iterations: int ): T =
46
- var previous: T
47
- result = HMAC [T](password, salt & INT_1 )
48
- previous = result
49
- for _ in 1 ..< iterations:
50
- previous = HMAC [T](password, $ previous)
51
- result ^= previous
52
-
53
18
proc newScramClient * [T](): ScramClient [T] =
54
19
result = new (ScramClient [T])
55
20
result .state = INITIAL
@@ -60,27 +25,24 @@ proc prepareFirstMessage*(s: ScramClient, username: string): string {.raises: [S
60
25
if username.isNilOrEmpty:
61
26
raise newException (ScramError , " username cannot be nil or empty" )
62
27
var username = username.replace (" =" , " =3D" ).replace (" ," , " =2C" )
63
- s.clientFirstBareMessage = " n="
64
- s.clientFirstBareMessage .add (username)
65
- s.clientFirstBareMessage .add (" ,r=" )
66
- s.clientFirstBareMessage .add (s.clientNonce)
28
+ s.clientFirstMessageBare = " n="
29
+ s.clientFirstMessageBare .add (username)
30
+ s.clientFirstMessageBare .add (" ,r=" )
31
+ s.clientFirstMessageBare .add (s.clientNonce)
67
32
68
- result = GS2_HEADER & s.clientFirstBareMessage
33
+ result = GS2_HEADER & s.clientFirstMessageBare
69
34
s.state = FIRST_PREPARED
70
35
71
36
proc prepareFinalMessage * [T](s: ScramClient [T], password, serverFirstMessage: string ): string {.raises : [ScramError , OverflowError , ValueError ].} =
72
37
if s.state != FIRST_PREPARED :
73
38
raise newException (ScramError , " First message have not been prepared, call prepareFirstMessage() first" )
74
-
75
39
var
76
40
nonce, salt: string
77
41
iterations: int
78
-
79
42
var matches: array [3 , string ]
80
43
if match (serverFirstMessage, SERVER_FIRST_MESSAGE , matches):
81
- # if serverFirstMessage =~ SERVER_FIRST_MESSAGE:
82
44
nonce = matches[0 ]
83
- salt = decode (matches[1 ])
45
+ salt = base64. decode (matches[1 ])
84
46
iterations = parseInt (matches[2 ])
85
47
else :
86
48
s.state = ENDED
@@ -91,28 +53,26 @@ proc prepareFinalMessage*[T](s: ScramClient[T], password, serverFirstMessage: st
91
53
return nil
92
54
93
55
let
94
- saltedPassword = s. hi (password, salt, iterations)
56
+ saltedPassword = hi [T] (password, salt, iterations)
95
57
clientKey = HMAC [T]($ saltedPassword, CLIENT_KEY )
96
58
storedKey = HASH [T]($ clientKey)
97
59
serverKey = HMAC [T]($ saltedPassword, SERVER_KEY )
98
- clientFinalMessageWithoutProof = " c=" & encode (GS2_HEADER ) & " ,r=" & nonce
99
- authMessage = s.clientFirstBareMessage & " ," & serverFirstMessage & " ," & clientFinalMessageWithoutProof
60
+ clientFinalMessageWithoutProof = " c=" & base64. encode (GS2_HEADER ) & " ,r=" & nonce
61
+ authMessage = s.clientFirstMessageBare & " ," & serverFirstMessage & " ," & clientFinalMessageWithoutProof
100
62
clientSignature = HMAC [T]($ storedKey, authMessage)
101
-
102
63
s.serverSignature = HMAC [T]($ serverKey, authMessage)
103
-
104
64
var clientProof = clientKey
105
65
clientProof ^= clientSignature
106
66
s.state = FINAL_PREPARED
107
- result = clientFinalMessageWithoutProof & " ,p=" & encode (clientProof, newLine= " " )
67
+ result = clientFinalMessageWithoutProof & " ,p=" & base64. encode (clientProof, newLine= " " )
108
68
109
69
proc verifyServerFinalMessage * (s: ScramClient , serverFinalMessage: string ): bool =
110
70
if s.state != FINAL_PREPARED :
111
71
raise newException (ScramError , " You can call this method only once after calling prepareFinalMessage()" )
112
72
s.state = ENDED
113
73
var matches: array [1 , string ]
114
74
if match (serverFinalMessage, SERVER_FINAL_MESSAGE , matches):
115
- let proposedServerSignature = decode (matches[0 ])
75
+ let proposedServerSignature = base64. decode (matches[0 ])
116
76
s.isSuccessful = proposedServerSignature == s.serverSignature
117
77
result = s.isSuccessful
118
78
0 commit comments