Normalization of cryptographic uses in asp.net
[mono.git] / mcs / class / System.Web / System.Web.Security / FormsAuthentication.cs
1 //
2 // System.Web.Security.FormsAuthentication
3 //
4 // Authors:
5 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //
7 // (C) 2002,2003 Ximian, Inc (http://www.ximian.com)
8 // Copyright (c) 2005 Novell, Inc (http://www.novell.com)
9 //
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System.Collections;
33 using System.IO;
34 using System.Security.Cryptography;
35 using System.Security.Permissions;
36 using System.Text;
37 using System.Web;
38 using System.Web.Configuration;
39 using System.Web.Util;
40 using System.Globalization;
41
42 namespace System.Web.Security
43 {
44         // CAS - no InheritanceDemand here as the class is sealed
45         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
46         public sealed class FormsAuthentication
47         {
48                 static string authConfigPath = "system.web/authentication";
49                 static string machineKeyConfigPath = "system.web/machineKey";
50 #if TARGET_J2EE
51                 const string Forms_initialized = "Forms.initialized";
52                 const string Forms_cookieName = "Forms.cookieName";
53                 const string Forms_cookiePath = "Forms.cookiePath";
54                 const string Forms_timeout = "Forms.timeout";
55                 const string Forms_protection = "Forms.protection";
56                 static bool initialized
57                 {
58                         get {
59                                 object o = AppDomain.CurrentDomain.GetData (Forms_initialized);
60                                 return o != null ? (bool) o : false;
61                         }
62                         set { AppDomain.CurrentDomain.SetData (Forms_initialized, value); }
63                 }
64                 static string cookieName
65                 {
66                         get { return (string) AppDomain.CurrentDomain.GetData (Forms_cookieName); }
67                         set { AppDomain.CurrentDomain.SetData (Forms_cookieName, value); }
68                 }
69                 static string cookiePath
70                 {
71                         get { return (string) AppDomain.CurrentDomain.GetData (Forms_cookiePath); }
72                         set { AppDomain.CurrentDomain.SetData (Forms_cookiePath, value); }
73                 }
74                 static int timeout
75                 {
76                         get {
77                                 object o = AppDomain.CurrentDomain.GetData (Forms_timeout);
78                                 return o != null ? (int) o : 0;
79                         }
80                         set { AppDomain.CurrentDomain.SetData (Forms_timeout, value); }
81                 }
82                 static FormsProtectionEnum protection
83                 {
84                         get { return (FormsProtectionEnum) AppDomain.CurrentDomain.GetData (Forms_protection); }
85                         set { AppDomain.CurrentDomain.SetData (Forms_protection, value); }
86                 }
87                 static object locker = new object ();
88 #else
89                 static bool initialized;
90                 static string cookieName;
91                 static string cookiePath;
92                 static int timeout;
93                 static FormsProtectionEnum protection;
94                 static object locker = new object ();
95 #endif
96 #if NET_1_1
97 #if TARGET_J2EE
98                 const string Forms_requireSSL = "Forms.requireSSL";
99                 const string Forms_slidingExpiration = "Forms.slidingExpiration";
100
101                 static bool requireSSL
102                 {
103                         get {
104                                 object o = AppDomain.CurrentDomain.GetData (Forms_requireSSL);
105                                 return o != null ? (bool) o : false;
106                         }
107                         set { AppDomain.CurrentDomain.SetData (Forms_requireSSL, value); }
108                 }
109                 static bool slidingExpiration
110                 {
111                         get {
112                                 object o = AppDomain.CurrentDomain.GetData (Forms_slidingExpiration);
113                                 return o != null ? (bool) o : false;
114                         }
115                         set { AppDomain.CurrentDomain.SetData (Forms_slidingExpiration, value); }
116                 }
117 #else
118                 static bool requireSSL;
119                 static bool slidingExpiration;
120 #endif
121 #endif
122 #if NET_2_0
123 #if TARGET_J2EE
124                 const string Forms_cookie_domain = "Forms.cookie_domain";
125                 const string Forms_cookie_mode = "Forms.cookie_mode";
126                 const string Forms_cookies_supported = "Forms.cookies_supported";
127                 const string Forms_default_url = "Forms.default_url";
128                 const string Forms_enable_crossapp_redirects = "Forms.enable_crossapp_redirects";
129                 const string Forms_login_url = "Forms.login_url";
130                 static string cookie_domain
131                 {
132                         get { return (string) AppDomain.CurrentDomain.GetData (Forms_cookie_domain); }
133                         set { AppDomain.CurrentDomain.SetData (Forms_cookie_domain, value); }
134                 }
135                 static HttpCookieMode cookie_mode
136                 {
137                         get { return (HttpCookieMode) AppDomain.CurrentDomain.GetData (Forms_cookie_mode); }
138                         set { AppDomain.CurrentDomain.SetData (Forms_cookie_mode, value); }
139                 }
140                 static bool cookies_supported
141                 {
142                         get {
143                                 object o = AppDomain.CurrentDomain.GetData (Forms_cookies_supported);
144                                 return o != null ? (bool) o : false;
145                         }
146                         set { AppDomain.CurrentDomain.SetData (Forms_cookies_supported, value); }
147                 }
148                 static string default_url
149                 {
150                         get { return (string) AppDomain.CurrentDomain.GetData (Forms_default_url); }
151                         set { AppDomain.CurrentDomain.SetData (Forms_default_url, value); }
152                 }
153                 static bool enable_crossapp_redirects
154                 {
155                         get {
156                                 object o = AppDomain.CurrentDomain.GetData (Forms_enable_crossapp_redirects);
157                                 return o != null ? (bool) o : false;
158                         }
159                         set { AppDomain.CurrentDomain.SetData (Forms_enable_crossapp_redirects, value); }
160                 }
161                 static string login_url
162                 {
163                         get { return (string) AppDomain.CurrentDomain.GetData (Forms_login_url); }
164                         set { AppDomain.CurrentDomain.SetData (Forms_login_url, value); }
165                 }
166 #else
167                 static string cookie_domain;
168                 static HttpCookieMode cookie_mode;
169                 static bool cookies_supported;
170                 static string default_url;
171                 static bool enable_crossapp_redirects;
172                 static string login_url;
173 #endif
174 #endif
175                 // same names and order used in xsp
176                 static string [] indexFiles = { "index.aspx",
177                                                 "Default.aspx",
178                                                 "default.aspx",
179                                                 "index.html",
180                                                 "index.htm" };
181
182                 public FormsAuthentication ()
183                 {
184                 }
185
186                 public static bool Authenticate (string name, string password)
187                 {
188                         if (name == null || password == null)
189                                 return false;
190
191                         Initialize ();
192                         HttpContext context = HttpContext.Current;
193                         if (context == null)
194                                 throw new HttpException ("Context is null!");
195
196                         name = name.ToLower (Helpers.InvariantCulture);
197 #if NET_2_0
198                         AuthenticationSection section = (AuthenticationSection) WebConfigurationManager.GetSection (authConfigPath);
199                         FormsAuthenticationCredentials config = section.Forms.Credentials;
200                         FormsAuthenticationUser user = config.Users[name];
201                         string stored = null;
202
203                         if (user != null)
204                                 stored = user.Password;
205 #else
206                         AuthConfig config = context.GetConfig (authConfigPath) as AuthConfig;
207                         Hashtable users = config.CredentialUsers;
208                         string stored = users [name] as string;
209 #endif
210                         if (stored == null)
211                                 return false;
212
213                         bool caseInsensitive = true;
214                         switch (config.PasswordFormat) {
215                         case FormsAuthPasswordFormat.Clear:
216                                 caseInsensitive = false;
217                                 /* Do nothing */
218                                 break;
219                         case FormsAuthPasswordFormat.MD5:
220                                 password = HashPasswordForStoringInConfigFile (password, FormsAuthPasswordFormat.MD5);
221                                 break;
222                         case FormsAuthPasswordFormat.SHA1:
223                                 password = HashPasswordForStoringInConfigFile (password, FormsAuthPasswordFormat.SHA1);
224                                 break;
225                         }
226
227                         return String.Compare (password, stored, caseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == 0;
228                 }
229
230                 static FormsAuthenticationTicket Decrypt2 (byte [] bytes)
231                 {
232                         if (protection == FormsProtectionEnum.None)
233                                 return FormsAuthenticationTicket.FromByteArray (bytes);
234
235 #if NET_2_0
236                         MachineKeySection config = (MachineKeySection) WebConfigurationManager.GetWebApplicationSection (machineKeyConfigPath);
237 #else
238                         MachineKeyConfig config = HttpContext.GetAppConfig (machineKeyConfigPath) as MachineKeyConfig;
239 #endif
240
241                         byte [] result = null;
242                         if (protection == FormsProtectionEnum.All) {
243                                 result = MachineKeySectionUtils.VerifyDecrypt (config, bytes);
244                         } else if (protection == FormsProtectionEnum.Encryption) {
245                                 result = MachineKeySectionUtils.Decrypt (config, bytes);
246                         } else if (protection == FormsProtectionEnum.Validation) {
247                                 result = MachineKeySectionUtils.Verify (config, bytes);
248                         }
249
250                         return FormsAuthenticationTicket.FromByteArray (result);
251                 }
252
253                 public static FormsAuthenticationTicket Decrypt (string encryptedTicket)
254                 {
255                         if (encryptedTicket == null || encryptedTicket == String.Empty)
256                                 throw new ArgumentException ("Invalid encrypted ticket", "encryptedTicket");
257
258                         Initialize ();
259
260                         FormsAuthenticationTicket ticket;
261                         byte [] bytes = Convert.FromBase64String (encryptedTicket);
262
263                         try {
264                                 ticket = Decrypt2 (bytes);
265                         } catch (Exception) {
266                                 ticket = null;
267                         }
268
269                         return ticket;
270                 }
271
272                 public static string Encrypt (FormsAuthenticationTicket ticket)
273                 {
274                         if (ticket == null)
275                                 throw new ArgumentNullException ("ticket");
276
277                         Initialize ();
278                         byte [] ticket_bytes = ticket.ToByteArray ();
279                         if (protection == FormsProtectionEnum.None)
280                                 return Convert.ToBase64String (ticket_bytes);
281
282                         byte [] result = null;
283 #if NET_2_0
284                         MachineKeySection config = (MachineKeySection) WebConfigurationManager.GetWebApplicationSection (machineKeyConfigPath);
285 #else
286                         MachineKeyConfig config = HttpContext.GetAppConfig (machineKeyConfigPath) as MachineKeyConfig;
287 #endif
288                         if (protection == FormsProtectionEnum.All) {
289                                 result = MachineKeySectionUtils.EncryptSign (config, ticket_bytes);
290                         } else if (protection == FormsProtectionEnum.Encryption) {
291                                 result = MachineKeySectionUtils.Encrypt (config, ticket_bytes);
292                         } else if (protection == FormsProtectionEnum.Validation) {
293                                 result = MachineKeySectionUtils.Sign (config, ticket_bytes);
294                         }
295
296                         return Convert.ToBase64String (result);
297                 }
298
299                 public static HttpCookie GetAuthCookie (string userName, bool createPersistentCookie)
300                 {
301                         return GetAuthCookie (userName, createPersistentCookie, null);
302                 }
303
304                 public static HttpCookie GetAuthCookie (string userName, bool createPersistentCookie, string strCookiePath)
305                 {
306                         Initialize ();
307
308                         if (userName == null)
309                                 userName = String.Empty;
310
311                         if (strCookiePath == null || strCookiePath.Length == 0)
312                                 strCookiePath = cookiePath;
313
314                         DateTime now = DateTime.Now;
315                         DateTime then;
316                         if (createPersistentCookie)
317                                 then = now.AddYears (50);
318                         else
319                                 then = now.AddMinutes (timeout);
320
321                         FormsAuthenticationTicket ticket = new FormsAuthenticationTicket (1,
322                                                                                           userName,
323                                                                                           now,
324                                                                                           then,
325                                                                                           createPersistentCookie,
326                                                                                           String.Empty,
327                                                                                           cookiePath);
328
329                         if (!createPersistentCookie)
330                                 then = DateTime.MinValue;
331
332                         HttpCookie cookie = new HttpCookie (cookieName, Encrypt (ticket), strCookiePath, then);
333                         if (requireSSL)
334                                 cookie.Secure = true;
335 #if NET_2_0
336                         if (!String.IsNullOrEmpty (cookie_domain))
337                                 cookie.Domain = cookie_domain;
338 #endif
339                         return cookie;
340                 }
341
342                 internal static string ReturnUrl
343                 {
344                         get { return HttpContext.Current.Request ["RETURNURL"]; }
345                 }
346
347                 public static string GetRedirectUrl (string userName, bool createPersistentCookie)
348                 {
349                         if (userName == null)
350                                 return null;
351
352                         Initialize ();
353                         HttpRequest request = HttpContext.Current.Request;
354                         string returnUrl = ReturnUrl;
355                         if (returnUrl != null)
356                                 return returnUrl;
357
358                         returnUrl = request.ApplicationPath;
359                         string apppath = request.PhysicalApplicationPath;
360                         bool found = false;
361
362                         foreach (string indexFile in indexFiles) {
363                                 string filePath = Path.Combine (apppath, indexFile);
364                                 if (File.Exists (filePath)) {
365                                         returnUrl = UrlUtils.Combine (returnUrl, indexFile);
366                                         found = true;
367                                         break;
368                                 }
369                         }
370
371                         if (!found)
372                                 returnUrl = UrlUtils.Combine (returnUrl, "index.aspx");
373
374                         return returnUrl;
375                 }
376
377                 static string HashPasswordForStoringInConfigFile (string password, FormsAuthPasswordFormat passwordFormat)
378                 {
379                         if (password == null)
380                                 throw new ArgumentNullException ("password");
381                         
382                         byte [] bytes;
383                         switch (passwordFormat) {
384                                 case FormsAuthPasswordFormat.MD5:
385                                         bytes = MD5.Create ().ComputeHash (Encoding.UTF8.GetBytes (password));
386                                         break;
387
388                                 case FormsAuthPasswordFormat.SHA1:
389                                         bytes = SHA1.Create ().ComputeHash (Encoding.UTF8.GetBytes (password));
390                                         break;
391
392                                 default:
393                                         throw new ArgumentException ("The format must be either MD5 or SHA1", "passwordFormat");
394                         }
395
396                         return MachineKeySectionUtils.GetHexString (bytes);
397                 }
398                 
399                 public static string HashPasswordForStoringInConfigFile (string password, string passwordFormat)
400                 {
401                         if (password == null)
402                                 throw new ArgumentNullException ("password");
403
404                         if (passwordFormat == null)
405                                 throw new ArgumentNullException ("passwordFormat");
406
407                         if (String.Compare (passwordFormat, "MD5", true, Helpers.InvariantCulture) == 0) {
408                                 return HashPasswordForStoringInConfigFile (password, FormsAuthPasswordFormat.MD5);
409                         } else if (String.Compare (passwordFormat, "SHA1", true, Helpers.InvariantCulture) == 0) {
410                                 return HashPasswordForStoringInConfigFile (password, FormsAuthPasswordFormat.SHA1);
411                         } else {
412                                 throw new ArgumentException ("The format must be either MD5 or SHA1", "passwordFormat");
413                         }
414                 }
415
416                 public static void Initialize ()
417                 {
418                         if (initialized)
419                                 return;
420
421                         lock (locker) {
422                                 if (initialized)
423                                         return;
424
425 #if NET_2_0
426                                 AuthenticationSection section = (AuthenticationSection)WebConfigurationManager.GetSection (authConfigPath);
427                                 FormsAuthenticationConfiguration config = section.Forms;
428
429                                 cookieName = config.Name;
430                                 timeout = (int)config.Timeout.TotalMinutes;
431                                 cookiePath = config.Path;
432                                 protection = config.Protection;
433                                 requireSSL = config.RequireSSL;
434                                 slidingExpiration = config.SlidingExpiration;
435                                 cookie_domain = config.Domain;
436                                 cookie_mode = config.Cookieless;
437                                 cookies_supported = true; /* XXX ? */
438                                 default_url = MapUrl(config.DefaultUrl);
439                                 enable_crossapp_redirects = config.EnableCrossAppRedirects;
440                                 login_url = MapUrl(config.LoginUrl);
441 #else
442                                 HttpContext context = HttpContext.Current;
443                                 AuthConfig authConfig = context.GetConfig (authConfigPath) as AuthConfig;
444                                 if (authConfig != null) {
445                                         cookieName = authConfig.CookieName;
446                                         timeout = authConfig.Timeout;
447                                         cookiePath = authConfig.CookiePath;
448                                         protection = authConfig.Protection;
449 #if NET_1_1
450                                         requireSSL = authConfig.RequireSSL;
451                                         slidingExpiration = authConfig.SlidingExpiration;
452 #endif
453                                 } else {
454                                         cookieName = ".MONOAUTH";
455                                         timeout = 30;
456                                         cookiePath = "/";
457                                         protection = FormsProtectionEnum.All;
458 #if NET_1_1
459                                         slidingExpiration = true;
460 #endif
461                                 }
462 #endif
463
464                                 initialized = true;
465                         }
466                 }
467
468 #if NET_2_0
469                 static string MapUrl (string url) {
470                         if (UrlUtils.IsRelativeUrl (url))
471                                 return UrlUtils.Combine (HttpRuntime.AppDomainAppVirtualPath, url);
472                         else
473                                 return UrlUtils.ResolveVirtualPathFromAppAbsolute (url);
474                 }
475 #endif
476
477                 public static void RedirectFromLoginPage (string userName, bool createPersistentCookie)
478                 {
479                         RedirectFromLoginPage (userName, createPersistentCookie, null);
480                 }
481
482                 public static void RedirectFromLoginPage (string userName, bool createPersistentCookie, string strCookiePath)
483                 {
484                         if (userName == null)
485                                 return;
486
487                         Initialize ();
488                         SetAuthCookie (userName, createPersistentCookie, strCookiePath);
489                         Redirect (GetRedirectUrl (userName, createPersistentCookie), false);
490                 }
491
492                 public static FormsAuthenticationTicket RenewTicketIfOld (FormsAuthenticationTicket tOld)
493                 {
494                         if (tOld == null)
495                                 return null;
496
497                         DateTime now = DateTime.Now;
498                         TimeSpan toIssue = now - tOld.IssueDate;
499                         TimeSpan toExpiration = tOld.Expiration - now;
500                         if (toExpiration > toIssue)
501                                 return tOld;
502
503                         FormsAuthenticationTicket tNew = tOld.Clone ();
504                         tNew.SetDates (now, now + (tOld.Expiration - tOld.IssueDate));
505                         return tNew;
506                 }
507
508                 public static void SetAuthCookie (string userName, bool createPersistentCookie)
509                 {
510                         Initialize ();
511                         SetAuthCookie (userName, createPersistentCookie, cookiePath);
512                 }
513
514                 public static void SetAuthCookie (string userName, bool createPersistentCookie, string strCookiePath)
515                 {
516                         HttpContext context = HttpContext.Current;
517                         if (context == null)
518                                 throw new HttpException ("Context is null!");
519
520                         HttpResponse response = context.Response;
521                         if (response == null)
522                                 throw new HttpException ("Response is null!");
523
524                         response.Cookies.Add (GetAuthCookie (userName, createPersistentCookie, strCookiePath));
525                 }
526
527                 public static void SignOut ()
528                 {
529                         Initialize ();
530
531                         HttpContext context = HttpContext.Current;
532                         if (context == null)
533                                 throw new HttpException ("Context is null!");
534
535                         HttpResponse response = context.Response;
536                         if (response == null)
537                                 throw new HttpException ("Response is null!");
538
539                         HttpCookieCollection cc = response.Cookies;
540                         cc.Remove (cookieName);
541                         HttpCookie expiration_cookie = new HttpCookie (cookieName, String.Empty);
542                         expiration_cookie.Expires = new DateTime (1999, 10, 12);
543                         expiration_cookie.Path = cookiePath;
544 #if NET_2_0
545                         if (!String.IsNullOrEmpty (cookie_domain))
546                                 expiration_cookie.Domain = cookie_domain;
547 #endif
548                         cc.Add (expiration_cookie);
549
550 #if NET_2_0
551                         Roles.DeleteCookie ();
552 #endif
553                 }
554
555                 public static string FormsCookieName
556                 {
557                         get {
558                                 Initialize ();
559                                 return cookieName;
560                         }
561                 }
562
563                 public static string FormsCookiePath
564                 {
565                         get {
566                                 Initialize ();
567                                 return cookiePath;
568                         }
569                 }
570 #if NET_1_1
571                 public static bool RequireSSL {
572                         get {
573                                 Initialize ();
574                                 return requireSSL;
575                         }
576                 }
577
578                 public static bool SlidingExpiration {
579                         get {
580                                 Initialize ();
581                                 return slidingExpiration;
582                         }
583                 }
584 #endif
585
586 #if NET_2_0
587                 public static string CookieDomain {
588                         get { Initialize (); return cookie_domain; }
589                 }
590
591                 public static HttpCookieMode CookieMode {
592                         get { Initialize (); return cookie_mode; }
593                 }
594
595                 public static bool CookiesSupported {
596                         get { Initialize (); return cookies_supported; }
597                 }
598
599                 public static string DefaultUrl {
600                         get { Initialize (); return default_url; }
601                 }
602
603                 public static bool EnableCrossAppRedirects {
604                         get { Initialize (); return enable_crossapp_redirects; }
605                 }
606
607                 public static string LoginUrl {
608                         get { Initialize (); return login_url; }
609                 }
610
611                 public static void RedirectToLoginPage ()
612                 {
613                         Redirect (LoginUrl);
614                 }
615
616                 [MonoTODO ("needs more tests")]
617                 public static void RedirectToLoginPage (string extraQueryString)
618                 {
619                         // TODO: if ? is in LoginUrl (legal?), ? in query (legal?) ...
620                         Redirect (LoginUrl + "?" + extraQueryString);
621                 }
622
623                 static void Redirect (string url)
624                 {
625                         HttpContext.Current.Response.Redirect (url);
626                 }
627 #endif
628
629                 static void Redirect (string url, bool end)
630                 {
631                         HttpContext.Current.Response.Redirect (url, end);
632                 }
633         }
634 }