Skip to content

Commit 0201fb1

Browse files
committed
add more algorithms
1 parent a708c3a commit 0201fb1

File tree

5 files changed

+88
-45
lines changed

5 files changed

+88
-45
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,13 @@
11
# scram
22
Salted Challenge Response Authentication Mechanism (SCRAM)
3+
4+
5+
```nim
6+
var s = newScramClient[Sha256Digest]()
7+
s.clientNonce = "VeAOLsQ22fn/tjalHQIz7cQT"
8+
9+
echo s.prepareFirstMessage("bob")
10+
let finalMessage = s.prepareFinalMessage("secret", "r=VeAOLsQ22fn/tjalHQIz7cQTmeE5qJh8qKEe8wALMut1,s=ldZSefTzKxPNJhP73AmW/A==,i=4096")
11+
echo finalMessage
12+
assert(finalMessage == "c=biws,r=VeAOLsQ22fn/tjalHQIz7cQTmeE5qJh8qKEe8wALMut1,p=AtNtxGzsMA8evcWBM0MXFjxN8OcG1KRkLkFyoHlupOU=")
13+
```

scram.nimble

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
version = "0.1.0"
1+
version = "0.1.1"
22
author = "Huy Doan"
33
description = "Salted Challenge Response Authentication Mechanism (SCRAM) "
44
license = "MIT"
55

6-
requires "nim >= 0.17.0", "hmac >= 0.1.2"
6+
requires "nim >= 0.17.0", "hmac >= 0.1.3"

scram/client.nim

Lines changed: 48 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,14 @@
1-
import base64, pegs, random, strutils, hmac, nimSHA2, private/utils
1+
import base64, pegs, random, strutils, hmac, nimSHA2, securehash, md5, private/[utils,types]
22

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
114

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
187
clientNonce: string
198
clientFirstBareMessage: string
20-
digestType: DigestType
219
state: ScramState
2210
isSuccessful: bool
23-
serverSignature: string
11+
serverSignature: T
2412

2513
const
2614
GS2_HEADER = "n,,"
@@ -32,28 +20,46 @@ let
3220
SERVER_FIRST_MESSAGE = peg"'r='{[^,]*}',s='{[^,]*}',i='{\d+}$"
3321
SERVER_FINAL_MESSAGE = peg"'v='{[^,]*}$"
3422

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)
3848
previous = result
3949
for _ in 1..<iterations:
40-
previous = $hmac_sha256(password, previous)
50+
previous = HMAC[T](password, $previous)
4151
result ^= previous
4252

43-
proc newScramClient*(digestType: DigestType): ScramClient =
44-
result = new(ScramClient)
53+
proc newScramClient*[T](): ScramClient[T] =
54+
result = new(ScramClient[T])
4555
result.state = INITIAL
46-
result.digestType = digestType
4756
result.clientNonce = makeNonce()
4857
result.isSuccessful = false
4958

5059
proc prepareFirstMessage*(s: ScramClient, username: string): string {.raises: [ScramError]} =
5160
if username.isNilOrEmpty:
5261
raise newException(ScramError, "username cannot be nil or empty")
53-
5462
var username = username.replace("=", "=3D").replace(",", "=2C")
55-
56-
5763
s.clientFirstBareMessage = "n="
5864
s.clientFirstBareMessage.add(username)
5965
s.clientFirstBareMessage.add(",r=")
@@ -62,15 +68,17 @@ proc prepareFirstMessage*(s: ScramClient, username: string): string {.raises: [S
6268
result = GS2_HEADER & s.clientFirstBareMessage
6369
s.state = FIRST_PREPARED
6470

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+
6675
var
6776
nonce, salt: string
6877
iterations: int
6978

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:
7482
nonce = matches[0]
7583
salt = decode(matches[1])
7684
iterations = parseInt(matches[2])
@@ -84,14 +92,14 @@ proc prepareFinalMessage*(s: ScramClient, password, serverFirstMessage: string):
8492

8593
let
8694
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)
9098
clientFinalMessageWithoutProof = "c=" & encode(GS2_HEADER) & ",r=" & nonce
9199
authMessage = s.clientFirstBareMessage & "," & serverFirstMessage & "," & clientFinalMessageWithoutProof
92-
clientSignature = $hmac_sha256(storedKey, authMessage)
100+
clientSignature = HMAC[T]($storedKey, authMessage)
93101

94-
s.serverSignature = $hmac_sha256(serverKey, authMessage)
102+
s.serverSignature = HMAC[T]($serverKey, authMessage)
95103

96104
var clientProof = clientKey
97105
clientProof ^= clientSignature
@@ -102,10 +110,10 @@ proc verifyServerFinalMessage*(s: ScramClient, serverFinalMessage: string): bool
102110
if s.state != FINAL_PREPARED:
103111
raise newException(ScramError, "You can call this method only once after calling prepareFinalMessage()")
104112
s.state = ENDED
105-
if serverFinalMessage =~ SERVER_FINAL_MESSAGE:
113+
var matches: array[1, string]
114+
if match(serverFinalMessage, SERVER_FINAL_MESSAGE, matches):
106115
let proposedServerSignature = decode(matches[0])
107116
s.isSuccessful = proposedServerSignature == s.serverSignature
108-
109117
result = s.isSuccessful
110118

111119
proc isSuccessful*(s: ScramClient): bool =
@@ -120,7 +128,7 @@ proc getState*(s: ScramClient): ScramState =
120128
result = s.state
121129

122130
when isMainModule:
123-
var s = newScramClient(SHA256)
131+
var s = newScramClient[Sha256Digest]()
124132
s.clientNonce = "VeAOLsQ22fn/tjalHQIz7cQT"
125133

126134
echo s.prepareFirstMessage("bob")

scram/private/types.nim

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
type
2+
ScramError* = object of SystemError
3+
4+
Sha1Digest* = array[20, uint8]
5+
6+
DigestType* = enum
7+
MD5
8+
SHA1
9+
SHA256
10+
SHA512
11+
12+
ScramState* = enum
13+
INITIAL
14+
FIRST_PREPARED
15+
FINAL_PREPARED
16+
ENDED

scram/private/utils.nim

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
import random, base64
1+
import random, base64, strutils, types
22
randomize()
33

4+
proc `$`*(sha: Sha1Digest): string =
5+
result = ""
6+
for v in sha:
7+
result.add(toHex(int(v), 2))
8+
49
proc makeNonce*(): string {.inline.} = result = encode($random(1.0))[0..^3]
510

611
template `^=`*[T](a, b: T) =
7-
for x in 0..<a.len:
8-
a[x] = (a[x].int32 xor b[x].int32).char
12+
for x in 0..<sizeof(a):
13+
when T is Sha1Digest:
14+
a[x] = (a[x].int32 xor b[x].int32).uint8
15+
else:
16+
a[x] = (a[x].int32 xor b[x].int32).char

0 commit comments

Comments
 (0)