1
- import strformat
2
- import base64, pegs, strutils, hmac, sha1, nimSHA2, md5, private/ [utils,types]
1
+ import base64, strformat, strutils, hmac, sha1, nimSHA2, md5, private/ [utils,types]
3
2
4
3
export MD5Digest , SHA1Digest , SHA224Digest , SHA256Digest , SHA384Digest , SHA512Digest , Keccak512Digest
4
+ export getChannelBindingData
5
5
6
6
type
7
7
ScramClient [T] = ref object of RootObj
10
10
state: ScramState
11
11
isSuccessful: bool
12
12
serverSignature: T
13
-
14
- when compileOption (" threads" ):
15
- var
16
- SERVER_FIRST_MESSAGE_VAL : ptr Peg
17
- SERVER_FINAL_MESSAGE_VAL : ptr Peg
18
- template SERVER_FIRST_MESSAGE : Peg =
19
- if SERVER_FIRST_MESSAGE_VAL .isNil:
20
- SERVER_FIRST_MESSAGE_VAL = cast [ptr Peg ](allocShared0 (sizeof (Peg )))
21
- SERVER_FIRST_MESSAGE_VAL [] = peg " 'r='{[^,]*}',s='{[^,]*}',i='{\ d+}$"
22
- SERVER_FIRST_MESSAGE_VAL []
23
- template SERVER_FINAL_MESSAGE : Peg =
24
- if SERVER_FINAL_MESSAGE_VAL .isNil:
25
- SERVER_FINAL_MESSAGE_VAL = cast [ptr Peg ](allocShared0 (sizeof (Peg )))
26
- SERVER_FINAL_MESSAGE_VAL [] = peg " 'v='{[^,]*}$"
27
- SERVER_FINAL_MESSAGE_VAL []
28
- else :
29
- let
30
- SERVER_FIRST_MESSAGE = peg " 'r='{[^,]*}',s='{[^,]*}',i='{\ d+}$"
31
- SERVER_FINAL_MESSAGE = peg " 'v='{[^,]*}$"
13
+ cbType: ChannelType
14
+ cbData: string
32
15
33
16
proc newScramClient * [T](): ScramClient [T] =
34
- result = new ( ScramClient [T])
17
+ result = new ScramClient [T]
35
18
result .clientNonce = makeNonce ()
19
+ result .cbType = TLS_NONE
20
+
21
+ proc setChannelBindingType * [T](s: ScramClient [T], channel: ChannelType ) = s.cbType = channel
22
+
23
+ proc setChannelBindingData * [T](s: ScramClient [T], data: string ) = s.cbData = data
36
24
37
25
proc prepareFirstMessage * (s: ScramClient , username: string ): string {.raises : [ScramError ]} =
38
26
if username.len == 0 :
@@ -44,7 +32,7 @@ proc prepareFirstMessage*(s: ScramClient, username: string): string {.raises: [S
44
32
s.clientFirstMessageBare.add (s.clientNonce)
45
33
46
34
s.state = FIRST_PREPARED
47
- GS2_HEADER & s.clientFirstMessageBare
35
+ result = makeGS2Header (s.cbType) & s.clientFirstMessageBare
48
36
49
37
proc prepareFinalMessage * [T](s: ScramClient [T], password, serverFirstMessage: string ): string =
50
38
if s.state != FIRST_PREPARED :
@@ -53,17 +41,15 @@ proc prepareFinalMessage*[T](s: ScramClient[T], password, serverFirstMessage: st
53
41
nonce, salt: string
54
42
iterations: int
55
43
var matches: array [3 , string ]
56
- if match (serverFirstMessage, SERVER_FIRST_MESSAGE , matches):
57
- for kv in serverFirstMessage.split (',' ):
58
- if kv[0 .. 1 ] == " i=" :
59
- iterations = parseInt (kv[2 ..^ 1 ])
60
- elif kv[0 .. 1 ] == " r=" :
61
- nonce = kv[2 ..^ 1 ]
62
- elif kv[0 .. 1 ] == " s=" :
63
- salt = base64.decode (kv[2 ..^ 1 ])
64
- else :
65
- s.state = ENDED
66
- return " "
44
+
45
+ for kv in serverFirstMessage.split (',' ):
46
+ if kv[0 .. 1 ] == " i=" :
47
+ iterations = parseInt (kv[2 ..^ 1 ])
48
+ elif kv[0 .. 1 ] == " r=" :
49
+ nonce = kv[2 ..^ 1 ]
50
+ elif kv[0 .. 1 ] == " s=" :
51
+ salt = base64.decode (kv[2 ..^ 1 ])
52
+
67
53
68
54
if not nonce.startsWith (s.clientNonce):
69
55
raise newException (ScramError , " Security error: invalid nonce received from server. Possible man-in-the-middle attack." )
@@ -76,7 +62,7 @@ proc prepareFinalMessage*[T](s: ScramClient[T], password, serverFirstMessage: st
76
62
clientKey = HMAC [T]($% saltedPassword, CLIENT_KEY )
77
63
storedKey = HASH [T]($% clientKey)
78
64
serverKey = HMAC [T]($% saltedPassword, SERVER_KEY )
79
- clientFinalMessageWithoutProof = " c=biws ,r=" & nonce
65
+ clientFinalMessageWithoutProof = makeCBind (s.cbType, s.cbData) & " ,r=" & nonce
80
66
authMessage = [s.clientFirstMessageBare, serverFirstMessage, clientFinalMessageWithoutProof].join (" ," )
81
67
clientSignature = HMAC [T]($% storedKey, authMessage)
82
68
s.serverSignature = HMAC [T]($% serverKey, authMessage)
@@ -93,12 +79,14 @@ proc verifyServerFinalMessage*(s: ScramClient, serverFinalMessage: string): bool
93
79
raise newException (ScramError , " You can call this method only once after calling prepareFinalMessage()" )
94
80
s.state = ENDED
95
81
var matches: array [1 , string ]
96
- if match (serverFinalMessage, SERVER_FINAL_MESSAGE , matches):
97
- var proposedServerSignature: string
98
- for kv in serverFinalMessage.split (',' ):
99
- if kv[0 .. 1 ] == " v=" :
100
- proposedServerSignature = base64.decode (kv[2 ..^ 1 ])
101
- s.isSuccessful = proposedServerSignature == $% s.serverSignature
82
+
83
+ var proposedServerSignature: string
84
+ for kv in serverFinalMessage.split (',' ):
85
+ if kv[0 .. 1 ] == " e=" :
86
+ raise newException (ScramError , " ServerError: " & kv[2 ..^ 1 ])
87
+ elif kv[0 .. 1 ] == " v=" :
88
+ proposedServerSignature = base64.decode (kv[2 ..^ 1 ])
89
+ s.isSuccessful = proposedServerSignature == $% s.serverSignature
102
90
s.isSuccessful
103
91
104
92
proc isSuccessful * (s: ScramClient ): bool =
0 commit comments