0 | module Data.Cryptography.SCRAM
2 | import Data.Cryptography.Hash
3 | import Data.Cryptography.HMAC
5 | import Data.String.Search
9 | import Data.String.Base64
10 | import Data.SortedMap as SortedMap
11 | import Control.Monad.State
13 | import Data.Buffer.Indexed as BufIndexed
16 | record Phase1State where
17 | constructor MkPhase1State
18 | clientNonceBase64: List Bits8
19 | clientFirstMessageBare: List Bits8
27 | genFirstMessageFromClient: String -> List Bits8 -> (List Bits8, Phase1State)
28 | genFirstMessageFromClient userName nonce =
29 | let bare = BufIndexed.toList (utf8Encode "n=") ++ BufIndexed.toList (utf8Encode userName) ++ BufIndexed.toList (utf8Encode ",r=") ++ base64Encode nonce
31 | (BufIndexed.toList (utf8Encode "n,,") ++ bare, MkPhase1State{clientFirstMessageBare=bare, clientNonceBase64=base64Encode nonce})
38 | | MkRNotPrefixedByClientNonce
39 | | MkRHasEmptyServerNonce
40 | | MkIterationsMissing
41 | | MkIterationCountUnsupported
44 | | MkSaltInvalidBase64
47 | Show Phase2Err where
48 | show MkRNotAtStart = "RNotAtStart"
49 | show MkRMissing = "RMissing"
50 | show MkREmpty = "REmpty"
51 | show MkRNotPrefixedByClientNonce = "RNotPrefixedByClientNonce"
52 | show MkRHasEmptyServerNonce = "RHasEmptyServerNonce"
53 | show MkIterationsMissing = "IterationsMissing"
54 | show MkIterationCountUnsupported = "IterationCountUnsupported"
55 | show MkSaltMissing = "SaltMissing"
56 | show MkSaltEmpty = "SaltEmpty"
57 | show MkSaltInvalidBase64 = "SaltInvalidBase64"
59 | record Phase2State (hash: HashAlgorithm) where
60 | constructor MkPhase2State
61 | expectedServerSignature: Vect hash.outputSize Bits8
63 | hmac : {hash: HashAlgorithm}
65 | -> Vect blockSize Bits8
67 | -> Vect hash.outputSize Bits8
68 | hmac key str = finalizeHmac $
appendHmac str $
mkHmacCtx {hash} {keyLength=blockSize} key
70 | bits32ToBigEndian : Bits32 -> Vect 4 Bits8
71 | bits32ToBigEndian streamIdent =
72 | [ cast (streamIdent `shiftR` 24)
73 | , cast (streamIdent `shiftR` 16)
74 | , cast (streamIdent `shiftR` 8)
78 | hi : {hash: HashAlgorithm}
80 | -> Vect blockSize Bits8
83 | -> Vect hash.outputSize Bits8
86 | root: Vect hash.outputSize Bits8
87 | root = hmac {hash} {blockSize} str (salt ++ toList (bits32ToBigEndian 1))
89 | ui : Int -> State (SortedMap Int (Vect hash.outputSize Bits8)) (Vect hash.outputSize Bits8)
95 | val <- hmac {hash} {blockSize} str . toList <$> ui (n - 1)
96 | modify (insert n val)
100 | uis : List (Vect hash.outputSize Bits8)
101 | uis = evalState SortedMap.empty $
traverse ui [1..n]
103 | foldr (zipWith xor) (replicate hash.outputSize 0) uis
105 | mkProof : {hash: HashAlgorithm}
106 | -> {blockSize: Nat}
107 | -> (Vect hash.outputSize Bits8 -> Vect blockSize Bits8)
108 | -> Vect blockSize Bits8
111 | mkProof zeroPad saltedPassword authMessage =
113 | clientKey = hmac {hash} saltedPassword (BufIndexed.toList (utf8Encode "Client Key"))
114 | storedKey: Vect hash.outputSize Bits8
115 | storedKey = hash.finalizeHash $
hash.appendHash (toList clientKey) hash.mkHashCtx
116 | clientSignature = hmac {hash} (zeroPad storedKey) authMessage
117 | clientProof: Vect hash.outputSize Bits8
118 | clientProof = zipWith xor clientKey clientSignature
119 | in toList clientProof
132 | recvFirstMessageFromServer : {hash: HashAlgorithm}
133 | -> {blockSize: Nat}
134 | -> (Vect hash.outputSize Bits8 -> Vect blockSize Bits8)
135 | -> Vect blockSize Bits8
138 | -> Either Phase2Err (List Bits8, Phase2State hash)
139 | recvFirstMessageFromServer zeroPad pw1 st1 msg = do
140 | Just ([], rBareAndAfter) <- pure $
splitBits8 (BufIndexed.toList (utf8Encode "r=")) msg
141 | | Nothing => Left MkRMissing
142 | | _ => Left MkRNotAtStart
143 | Just (fullNonceBase64, sBareAndAfter) <- pure $
splitBits8 (BufIndexed.toList (utf8Encode ",s=")) rBareAndAfter
144 | | Nothing => Left MkSaltMissing
145 | S _ <- pure $
length fullNonceBase64
146 | | Z => Left MkREmpty
147 | True <- pure $
st1.clientNonceBase64 `isPrefixOf` fullNonceBase64
148 | | False => Left MkRNotPrefixedByClientNonce
149 | False <- pure $
length st1.clientNonceBase64 == length fullNonceBase64
150 | | True => Left MkRHasEmptyServerNonce
151 | Just (saltBase64, iterationsDecimal) <- pure $
splitBits8 (BufIndexed.toList (utf8Encode ",i=")) sBareAndAfter
152 | | Nothing => Left MkIterationsMissing
153 | case base64DecodeBits8 saltBase64 of
154 | Just [] => Left MkSaltEmpty
155 | Nothing => Left MkSaltInvalidBase64
156 | Just nonEmptySalt =>
157 | case utf8Decode (BufIndexed.bufferL iterationsDecimal) of
160 | clientFinalMessageWithoutProof =
161 | BufIndexed.toList (utf8Encode "c=biws,r=") ++ fullNonceBase64
163 | st1.clientFirstMessageBare
164 | ++ BufIndexed.toList (utf8Encode ",") ++ msg
165 | ++ BufIndexed.toList (utf8Encode ",") ++ clientFinalMessageWithoutProof
166 | saltedPassword : Vect blockSize Bits8
167 | saltedPassword = zeroPad (hi pw1 nonEmptySalt 4096)
168 | clientProofBase64 = base64Encode $
mkProof zeroPad saltedPassword authMessage
169 | serverKey = hmac {hash} saltedPassword (BufIndexed.toList (utf8Encode "Server Key"))
170 | expectedServerSignature = hmac {hash} (zeroPad serverKey) authMessage
172 | (clientFinalMessageWithoutProof ++ BufIndexed.toList (utf8Encode ",p=") ++ clientProofBase64
174 | { expectedServerSignature =
175 | expectedServerSignature
178 | _ => Left MkIterationCountUnsupported
182 | = MkServerSignatureInvalidBase64
183 | | MkServerSignatureMissing
184 | | MkServerSignatureMismatch
187 | Show Phase3Err where
188 | show MkServerSignatureInvalidBase64 = "ServerSignatureInvalidBase64"
189 | show MkServerSignatureMissing = "ServerSignatureMissing"
190 | show MkServerSignatureMismatch = "ServerSignatureMismatch"
194 | recvSecondMessageFromServer: {hash: HashAlgorithm} -> Phase2State hash -> List Bits8 -> Maybe Phase3Err
195 | recvSecondMessageFromServer st2 receivedASCII =
196 | if BufIndexed.toList (utf8Encode "v=") `isPrefixOf` receivedASCII
198 | case base64DecodeBits8 (drop 2 receivedASCII) of
199 | Nothing => Just MkServerSignatureInvalidBase64
200 | Just decodedServerSignature =>
201 | if decodedServerSignature == toList st2.expectedServerSignature
203 | else Just MkServerSignatureMismatch
204 | else Just MkServerSignatureMissing