1
- import base64, pegs, random, strutils, hmac, nimSHA2, private/ utils
1
+ import base64, pegs, random, strutils, hmac, nimSHA2, securehash, md5, private/ [ utils,types]
2
2
3
- type
4
- ScramError * = object of SystemError
5
-
6
- ScramState = enum
7
- INITIAL
8
- FIRST_PREPARED
9
- FINAL_PREPARED
10
- ENDED
3
+ export MD5Digest , Sha256Digest , Sha512Digest
11
4
12
- DigestType * = enum
13
- SHA1
14
- SHA256
15
- SHA512
16
-
17
- ScramClient = ref object of RootObj
5
+ type
6
+ ScramClient [T] = ref object of RootObj
18
7
clientNonce: string
19
8
clientFirstBareMessage: string
20
- digestType: DigestType
21
9
state: ScramState
22
10
isSuccessful: bool
23
- serverSignature: string
11
+ serverSignature: T
24
12
25
13
const
26
14
GS2_HEADER = " n,,"
32
20
SERVER_FIRST_MESSAGE = peg " 'r='{[^,]*}',s='{[^,]*}',i='{\ d+}$"
33
21
SERVER_FINAL_MESSAGE = peg " 'v='{[^,]*}$"
34
22
35
- proc hi (s: ScramClient , password, salt: string , iterations: int ): string =
36
- var previous: string
37
- result = $ hmac_sha256 (password, salt & INT_1 )
23
+
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 )
38
48
previous = result
39
49
for _ in 1 ..< iterations:
40
- previous = $ hmac_sha256 (password, previous)
50
+ previous = HMAC [T] (password, $ previous)
41
51
result ^= previous
42
52
43
- proc newScramClient * (digestType: DigestType ): ScramClient =
44
- result = new (ScramClient )
53
+ proc newScramClient * [T]( ): ScramClient [T] =
54
+ result = new (ScramClient [T] )
45
55
result .state = INITIAL
46
- result .digestType = digestType
47
56
result .clientNonce = makeNonce ()
48
57
result .isSuccessful = false
49
58
50
59
proc prepareFirstMessage * (s: ScramClient , username: string ): string {.raises : [ScramError ]} =
51
60
if username.isNilOrEmpty:
52
61
raise newException (ScramError , " username cannot be nil or empty" )
53
-
54
62
var username = username.replace (" =" , " =3D" ).replace (" ," , " =2C" )
55
-
56
-
57
63
s.clientFirstBareMessage = " n="
58
64
s.clientFirstBareMessage.add (username)
59
65
s.clientFirstBareMessage.add (" ,r=" )
@@ -62,15 +68,17 @@ proc prepareFirstMessage*(s: ScramClient, username: string): string {.raises: [S
62
68
result = GS2_HEADER & s.clientFirstBareMessage
63
69
s.state = FIRST_PREPARED
64
70
65
- proc prepareFinalMessage * (s: ScramClient , password, serverFirstMessage: string ): string {.raises : [ScramError , OverflowError , ValueError ].} =
71
+ proc prepareFinalMessage * [T](s: ScramClient [T], password, serverFirstMessage: string ): string {.raises : [ScramError , OverflowError , ValueError ].} =
72
+ if s.state != FIRST_PREPARED :
73
+ raise newException (ScramError , " First message have not been prepared, call prepareFirstMessage() first" )
74
+
66
75
var
67
76
nonce, salt: string
68
77
iterations: int
69
78
70
- if s.state != FIRST_PREPARED :
71
- raise newException (ScramError , " First message have not been prepared, call prepareFirstMessage() first" )
72
-
73
- if serverFirstMessage =~ SERVER_FIRST_MESSAGE :
79
+ var matches: array [3 , string ]
80
+ if match (serverFirstMessage, SERVER_FIRST_MESSAGE , matches):
81
+ # if serverFirstMessage =~ SERVER_FIRST_MESSAGE:
74
82
nonce = matches[0 ]
75
83
salt = decode (matches[1 ])
76
84
iterations = parseInt (matches[2 ])
@@ -84,14 +92,14 @@ proc prepareFinalMessage*(s: ScramClient, password, serverFirstMessage: string):
84
92
85
93
let
86
94
saltedPassword = s.hi (password, salt, iterations)
87
- clientKey = $ hmac_sha256 ( saltedPassword, CLIENT_KEY )
88
- storedKey = $ computeSHA256 ( clientKey)
89
- serverKey = $ hmac_sha256 ( saltedPassword, SERVER_KEY )
95
+ clientKey = HMAC [T]( $ saltedPassword, CLIENT_KEY )
96
+ storedKey = HASH [T]( $ clientKey)
97
+ serverKey = HMAC [T]( $ saltedPassword, SERVER_KEY )
90
98
clientFinalMessageWithoutProof = " c=" & encode (GS2_HEADER ) & " ,r=" & nonce
91
99
authMessage = s.clientFirstBareMessage & " ," & serverFirstMessage & " ," & clientFinalMessageWithoutProof
92
- clientSignature = $ hmac_sha256 ( storedKey, authMessage)
100
+ clientSignature = HMAC [T]( $ storedKey, authMessage)
93
101
94
- s.serverSignature = $ hmac_sha256 ( serverKey, authMessage)
102
+ s.serverSignature = HMAC [T]( $ serverKey, authMessage)
95
103
96
104
var clientProof = clientKey
97
105
clientProof ^= clientSignature
@@ -102,10 +110,10 @@ proc verifyServerFinalMessage*(s: ScramClient, serverFinalMessage: string): bool
102
110
if s.state != FINAL_PREPARED :
103
111
raise newException (ScramError , " You can call this method only once after calling prepareFinalMessage()" )
104
112
s.state = ENDED
105
- if serverFinalMessage =~ SERVER_FINAL_MESSAGE :
113
+ var matches: array [1 , string ]
114
+ if match (serverFinalMessage, SERVER_FINAL_MESSAGE , matches):
106
115
let proposedServerSignature = decode (matches[0 ])
107
116
s.isSuccessful = proposedServerSignature == s.serverSignature
108
-
109
117
result = s.isSuccessful
110
118
111
119
proc isSuccessful * (s: ScramClient ): bool =
@@ -120,7 +128,7 @@ proc getState*(s: ScramClient): ScramState =
120
128
result = s.state
121
129
122
130
when isMainModule :
123
- var s = newScramClient ( SHA256 )
131
+ var s = newScramClient [ Sha256Digest ]( )
124
132
s.clientNonce = " VeAOLsQ22fn/tjalHQIz7cQT"
125
133
126
134
echo s.prepareFirstMessage (" bob" )
0 commit comments