23
23
namespace MongoDB . Driver . Communication . Security . Mechanisms
24
24
{
25
25
/// <summary>
26
- /// A mechanism for DIGEST-MD5.
26
+ /// A mechanism for DIGEST-MD5 (http://www.ietf.org/rfc/rfc2831.txt) .
27
27
/// </summary>
28
28
internal class DigestMD5Mechanism : ISaslMechanism
29
29
{
@@ -63,6 +63,299 @@ public ISaslStep Initialize(Internal.MongoConnection connection, MongoCredential
63
63
connection . ServerInstance . Address . Host ,
64
64
credential . Username ,
65
65
( ( PasswordEvidence ) credential . Evidence ) . Password ) ;
66
- }
66
+ }
67
+
68
+ // nested classes
69
+ private class ManagedDigestMD5Implementation : SaslImplementationBase , ISaslStep
70
+ {
71
+ // private fields
72
+ private readonly byte [ ] _cnonce ;
73
+ private readonly string _digestUri ;
74
+ private readonly string _nonceCount ;
75
+ private readonly string _qop ;
76
+ private readonly string _password ;
77
+ private readonly string _username ;
78
+
79
+ // constructors
80
+ public ManagedDigestMD5Implementation ( string serverName , string username , string password )
81
+ {
82
+ _cnonce = CreateClientNonce ( ) ;
83
+ _digestUri = "mongodb/" + serverName ;
84
+ _nonceCount = "00000001" ;
85
+ _qop = "auth" ;
86
+ _password = password ;
87
+ _username = username ;
88
+ }
89
+
90
+ // public properties
91
+ public byte [ ] BytesToSendToServer
92
+ {
93
+ get { return new byte [ 0 ] ; }
94
+ }
95
+
96
+ // public methods
97
+ public ISaslStep Transition ( SaslConversation conversation , byte [ ] bytesReceivedFromServer )
98
+ {
99
+ var directives = DirectiveParser . Parse ( bytesReceivedFromServer ) ;
100
+ var encoding = Encoding . UTF8 ;
101
+
102
+ var sb = new StringBuilder ( ) ;
103
+ sb . AppendFormat ( "username=\" {0}\" " , _username ) ;
104
+ sb . AppendFormat ( ",nonce=\" {0}\" " , encoding . GetString ( directives [ "nonce" ] ) . Replace ( "\" " , "\\ \" " ) ) ;
105
+ sb . AppendFormat ( ",cnonce=\" {0}\" " , encoding . GetString ( _cnonce ) . Replace ( "\" " , "\\ \" " ) ) ;
106
+ sb . AppendFormat ( ",nc={0}" , _nonceCount ) ;
107
+ sb . AppendFormat ( ",qop={0}" , _qop ) ;
108
+ sb . AppendFormat ( ",digest-uri=\" {0}\" " , _digestUri ) ;
109
+ sb . AppendFormat ( ",response={0}" , ComputeResponse ( encoding , directives [ "nonce" ] ) ) ;
110
+ sb . Append ( ",charset=\" utf-8\" " ) ;
111
+
112
+ return new ManagedDigestMD5FinalStep ( encoding . GetBytes ( sb . ToString ( ) ) ) ;
113
+ }
114
+
115
+ // private methods
116
+ private string ComputeResponse ( Encoding encoding , byte [ ] nonce )
117
+ {
118
+ using ( var md5 = MD5 . Create ( ) )
119
+ {
120
+ var a1 = ComputeA1 ( encoding , md5 , nonce ) ;
121
+ var a2 = ComputeA2 ( encoding ) ;
122
+
123
+ var a1Hash = md5 . ComputeHash ( a1 ) ;
124
+ var a2Hash = md5 . ComputeHash ( a2 ) ;
125
+
126
+ var a1Hex = ToHexString ( a1Hash ) ;
127
+ var a2Hex = ToHexString ( a2Hash ) ;
128
+
129
+ var kd = new List < byte > ( ) ;
130
+ kd . AddRange ( encoding . GetBytes ( a1Hex ) ) ;
131
+ kd . Add ( ( byte ) ':' ) ;
132
+ kd . AddRange ( nonce ) ;
133
+ kd . Add ( ( byte ) ':' ) ;
134
+ kd . AddRange ( encoding . GetBytes ( _nonceCount ) ) ;
135
+ kd . Add ( ( byte ) ':' ) ;
136
+ kd . AddRange ( _cnonce ) ;
137
+ kd . Add ( ( byte ) ':' ) ;
138
+ kd . AddRange ( encoding . GetBytes ( _qop ) ) ;
139
+ kd . Add ( ( byte ) ':' ) ;
140
+ kd . AddRange ( encoding . GetBytes ( a2Hex ) ) ;
141
+
142
+ var kdHash = md5 . ComputeHash ( kd . ToArray ( ) ) ;
143
+ return ToHexString ( kdHash ) ;
144
+ }
145
+ }
146
+
147
+ private byte [ ] ComputeA1 ( Encoding encoding , MD5 md5 , byte [ ] nonce )
148
+ {
149
+ // User Token
150
+ var userToken = new List < byte > ( ) ;
151
+ userToken . AddRange ( encoding . GetBytes ( _username ) ) ;
152
+ userToken . Add ( ( byte ) ':' ) ;
153
+ userToken . Add ( ( byte ) ':' ) ;
154
+ var passwordBytes = GetMongoPassword ( md5 , encoding , _username , _password ) ;
155
+ var passwordHex = ToHexString ( passwordBytes ) ;
156
+ userToken . AddRange ( encoding . GetBytes ( passwordHex ) ) ;
157
+ var userTokenBytes = md5 . ComputeHash ( userToken . ToArray ( ) ) ;
158
+
159
+ var nonceBytes = new List < byte > ( ) ;
160
+ nonceBytes . Add ( ( byte ) ':' ) ;
161
+ nonceBytes . AddRange ( nonce ) ;
162
+ nonceBytes . Add ( ( byte ) ':' ) ;
163
+ nonceBytes . AddRange ( _cnonce ) ;
164
+
165
+ var result = new byte [ userTokenBytes . Length + nonceBytes . Count ] ;
166
+ userTokenBytes . CopyTo ( result , 0 ) ;
167
+ nonceBytes . CopyTo ( result , userTokenBytes . Length ) ;
168
+
169
+ return result ;
170
+ }
171
+
172
+ private byte [ ] ComputeA2 ( Encoding encoding )
173
+ {
174
+ return encoding . GetBytes ( "AUTHENTICATE:" + _digestUri ) ;
175
+ }
176
+
177
+ private byte [ ] CreateClientNonce ( )
178
+ {
179
+ return Encoding . UTF8 . GetBytes ( new Random ( ) . Next ( 1234000 , 99999999 ) . ToString ( ) ) ;
180
+ }
181
+ }
182
+
183
+ private class ManagedDigestMD5FinalStep : ISaslStep
184
+ {
185
+ private readonly byte [ ] _bytesToSendToServer ;
186
+
187
+ public ManagedDigestMD5FinalStep ( byte [ ] bytesToSendToServer )
188
+ {
189
+ _bytesToSendToServer = bytesToSendToServer ;
190
+ }
191
+
192
+ public byte [ ] BytesToSendToServer
193
+ {
194
+ get { return _bytesToSendToServer ; }
195
+ }
196
+
197
+ public ISaslStep Transition ( SaslConversation conversation , byte [ ] bytesReceivedFromServer )
198
+ {
199
+ return new SaslCompletionStep ( new byte [ 0 ] ) ;
200
+ }
201
+ }
202
+
203
+ private static class DirectiveParser
204
+ {
205
+ public static Dictionary < string , byte [ ] > Parse ( byte [ ] bytes )
206
+ {
207
+ var s = Encoding . UTF8 . GetString ( bytes ) ;
208
+
209
+ var parsed = new Dictionary < string , byte [ ] > ( ) ;
210
+ var index = 0 ;
211
+ while ( index < bytes . Length )
212
+ {
213
+ var key = ParseKey ( bytes , ref index ) ;
214
+ SkipWhitespace ( bytes , ref index ) ;
215
+ if ( bytes [ index ] != '=' )
216
+ {
217
+ throw new MongoSecurityException ( string . Format ( "Expected a '=' after a key \" {0}\" ." ) ) ;
218
+ }
219
+ else if ( key . Length == 0 )
220
+ {
221
+ throw new MongoSecurityException ( "Empty directive key." ) ;
222
+ }
223
+ index ++ ; // skip =
224
+
225
+ var value = ParseValue ( key , bytes , ref index ) ;
226
+ parsed . Add ( key , value ) ;
227
+ if ( index >= bytes . Length )
228
+ {
229
+ break ;
230
+ }
231
+ else if ( bytes [ index ] != ',' )
232
+ {
233
+ throw new MongoSecurityException ( string . Format ( "Expected a ',' after directive \" {0}\" ." , key ) ) ;
234
+ }
235
+ else
236
+ {
237
+ index ++ ;
238
+ SkipWhitespace ( bytes , ref index ) ;
239
+ }
240
+ }
241
+
242
+ return parsed ;
243
+ }
244
+
245
+ private static string ParseKey ( byte [ ] bytes , ref int index )
246
+ {
247
+ var key = new StringBuilder ( ) ;
248
+ while ( index < bytes . Length )
249
+ {
250
+ var b = bytes [ index ] ;
251
+ if ( b == ',' )
252
+ {
253
+ if ( key . Length == 0 )
254
+ {
255
+ // there were some extra commas, so we skip over them
256
+ // and try to find the next key
257
+ index ++ ;
258
+ SkipWhitespace ( bytes , ref index ) ;
259
+ }
260
+ else
261
+ {
262
+ throw new MongoSecurityException ( string . Format ( "Directive key \" {0}\" contains a ','." , key . ToString ( ) ) ) ;
263
+ }
264
+ }
265
+ else if ( b == '=' )
266
+ {
267
+ break ;
268
+ }
269
+ else if ( IsWhiteSpace ( b ) )
270
+ {
271
+ index ++ ;
272
+ break ;
273
+ }
274
+ else
275
+ {
276
+ index ++ ;
277
+ key . Append ( ( char ) b ) ;
278
+ }
279
+ }
280
+
281
+ return key . ToString ( ) ;
282
+ }
283
+
284
+ private static byte [ ] ParseValue ( string key , byte [ ] bytes , ref int index )
285
+ {
286
+ List < byte > value = new List < byte > ( ) ;
287
+ bool isQuoted = false ;
288
+ while ( index < bytes . Length )
289
+ {
290
+ var b = bytes [ index ] ;
291
+ if ( b == '\\ ' )
292
+ {
293
+ index ++ ; // skip escape
294
+ if ( index < bytes . Length )
295
+ {
296
+ value . Add ( b ) ;
297
+ index ++ ;
298
+ }
299
+ else
300
+ {
301
+ throw new MongoSecurityException ( string . Format ( "Unmatched quote found in value of directive key \" {0}\" ." , key ) ) ;
302
+ }
303
+ }
304
+ else if ( b == '"' )
305
+ {
306
+ index ++ ;
307
+ if ( isQuoted )
308
+ {
309
+ // we have closed the quote...
310
+ break ;
311
+ }
312
+ else if ( value . Count == 0 )
313
+ {
314
+ isQuoted = true ;
315
+ }
316
+ else
317
+ {
318
+ // quote in the middle of an unquoted string
319
+ value . Add ( b ) ;
320
+ }
321
+ }
322
+ else
323
+ {
324
+ if ( b == ',' && ! isQuoted )
325
+ {
326
+ break ;
327
+ }
328
+ value . Add ( b ) ;
329
+ index ++ ;
330
+ }
331
+ }
332
+
333
+ return value . ToArray ( ) ;
334
+ }
335
+
336
+ private static bool IsWhiteSpace ( byte b )
337
+ {
338
+ switch ( b )
339
+ {
340
+ case 13 : // US-ASCII CR, carriage return
341
+ case 10 : // US-ASCII LF, linefeed
342
+ case 32 : // US-ASCII SP, space
343
+ case 9 : // US-ASCII HT, horizontal-tab
344
+ return true ;
345
+ }
346
+ return false ;
347
+ }
348
+
349
+ private static void SkipWhitespace ( byte [ ] bytes , ref int index )
350
+ {
351
+ for ( ; index < bytes . Length ; index ++ )
352
+ {
353
+ if ( ! IsWhiteSpace ( bytes [ index ] ) )
354
+ {
355
+ return ;
356
+ }
357
+ }
358
+ }
359
+ }
67
360
}
68
- }
361
+ }
0 commit comments