Exemplo n.º 1
0
        /*
         * Tries to login with the given cookie as credentials
         */
        static void LoginFromCookie(HttpCookie cookie, ApplicationContext context)
        {
            // User has persistent cookie associated with client
            var cookieSplits = cookie.Value.Split(' ');

            if (cookieSplits.Length != 2)
            {
                throw new SecurityException("Cookie not accepted");
            }

            // Retrieving username and (hashed/salted) password from cookie.
            var cookieUsername = cookieSplits [0];
            var cookiePassword = cookieSplits [1];

            // Retrieving password file in Node format.
            var pwdFile = AuthFile.GetAuthFile(context);

            // Checking if user exist.
            var userNode = pwdFile ["users"] [cookieUsername];

            if (userNode == null)
            {
                throw new SecurityException("Cookie not accepted");
            }

            /*
             * Checking if user's password is a match.
             *
             * Notice, we need to double hash the password from "auth", since the
             * reference stored in cookie is double hashed, and has the client's fingerprint
             * added to it, to reduce probability of cookie theft.
             *
             * Notice also that since the credential cookie has roughly 1.0e+77 amount of
             * entropy, slow hashing or using blow fish at this point is pointless, and
             * would simply add additional overhead for the initial loading of our page
             * when the persistent credential cookie needs to be verified. Hence we
             * can safely get away with "fast hashing" at this point, but adding parts
             * of the clients fingerprint into the mix, to avoid at least to some extent
             * credential cookie theft.
             */
            if (cookiePassword == Passwords.HashPasswordForCookieStorage(
                    context,
                    userNode ["password"].Get <string> (context)))
            {
                // MATCH, discarding previous Context Ticket and creating a new Ticket.
                SetTicket(context, new ContextTicket(userNode.Name, userNode ["role"].Get <string> (context), false));

                // Evaluates user's [.onlogin] section, if it exists.
                EvaluateOnLoginIfExisting(context);
            }
            else
            {
                // Catched above, which destroys cookie, and associates the default context with user.
                throw new Exception();
            }
        }
Exemplo n.º 2
0
        /*
         * Tries to login user according to given user credentials.
         */
        public static void Login(ApplicationContext context, Node args)
        {
            // Defaulting result of Active Event to unsuccessful.
            args.Value = false;

            // Retrieving supplied credentials.
            var username = args.GetExChildValue <string> ("username", context);
            var password = args.GetExChildValue <string> ("password", context);

            args.FindOrInsert("password").Value = "xxx";  // In case an exception occurs.
            var persist = args.GetExChildValue("persist", context, false);

            // Retrieving password file as a Node.
            var pwdFile = AuthFile.GetAuthFile(context);

            /*
             * Checking for match on specified username.
             */
            var userNode = pwdFile ["users"] [username];

            if (userNode == null)
            {
                // Username doesn't exist.
                throw new LambdaSecurityException(_credentialsNotAcceptedException, args, context);
            }

            /*
             * Checking if current username has attempted to login just recently, and the
             * configured timespan for each successive login attempt per user, has not passed.
             *
             * This should be able to provide some rudimentary defense from a "brute force password attack".
             *
             * Notice, we do this after we have checked if the username exists, to avoid having an adversary
             * flood the server's cache with bogus usernames associated with DateTime objects.
             * We also us the web cache, which (of course) means the object will only exists for some
             * time, defaulting to the settings of the web cache for your system.
             */
            var cooldown = context.RaiseEvent(
                ".p5.config.get",
                new Node(".p5.config.get", _cooldownPeriodConfigName)) [0]?.Get(context, -1) ?? -1;

            if (cooldown != -1)
            {
                // User has configured the system to have a "cooldown period" for successive login attempts.
                var bruteForceLastAttempt = new Node(".p5.web.cache.get", _bruteForceCacheName + username);
                var lastAttemptNode       = context.RaiseEvent(".p5.web.cache.get", bruteForceLastAttempt);
                if (lastAttemptNode.Count > 0)
                {
                    // Previous attempt has been attempted recently.
                    var date            = lastAttemptNode [0].Get <DateTime> (context, DateTime.MinValue);
                    var timeSpanSeconds = Convert.ToInt32((DateTime.Now - date).TotalSeconds);
                    if (timeSpanSeconds < cooldown)
                    {
                        // Cooldown period has not passed.
                        throw new LambdaException("You need to wait " + (cooldown - timeSpanSeconds) + " seconds before you can try again", args, context);
                    }
                }
            }

            // Checking for match on password.
            if (!Passwords.VerifyPasswordIsCorrect(password, userNode ["password"].Get <string> (context)))
            {
                /*
                 * Making sure we guard against brute force password attacks, before we throw security exception.
                 *
                 * Notice, this prevents the same username from attempting to login more than once every n seconds,
                 * which is configurable in the config file of the app.
                 */
                var bruteForceLastAttempt = new Node(".p5.web.cache.set", _bruteForceCacheName + username);
                bruteForceLastAttempt.Add("src", DateTime.Now);
                context.RaiseEvent(".p5.web.cache.set", bruteForceLastAttempt);
                throw new LambdaSecurityException(_credentialsNotAcceptedException, args, context);
            }

            // Success, creating our context ticket.
            var role = userNode ["role"].Get <string> (context);

            SetTicket(context, new ContextTicket(username, role, false));

            // Signaling success to caller.
            args.Value = true;

            // Checking if we should create persistent cookie on disc to remember username/password for given client.
            if (persist)
            {
                // Caller wants to create persistent cookie to remember username/password.
                var cookie = new HttpCookie(_credentialCookieName);
                cookie.Expires = DateTime.Now.AddDays(context.RaiseEvent(
                                                          ".p5.config.get",
                                                          new Node(".p5.config.get", "p5.auth.credential-cookie-valid")) [0].Get <int> (context));

                // To avoid JavaScript access to credential cookie.
                cookie.HttpOnly = true;

                /*
                 * The value of our cookie is in "username hashed-password" format.
                 *
                 * This is an entropy of roughly 1.1579e+77, making a brute force attack
                 * impossible, at least without a Rainbow/Dictionary attack, which should
                 * be effectively prevented, by having a single static server salt,
                 * which again is cryptographically secured and persisted to disc
                 * in the "auth" file, and hence normally inaccessible for an adversary.
                 *
                 * Notice, we double hash the password we store in our cookie, to make
                 * sure we never expose the parts of our password we store in our "auth" file.
                 */
                cookie.Value = username + " " + Passwords.HashPasswordForCookieStorage(
                    context,
                    userNode ["password"].Get <string> (context));
                HttpContext.Current.Response.Cookies.Add(cookie);
            }

            // Evaluates user's [.onlogin] section, if it exists.
            EvaluateOnLoginIfExisting(context);
        }