コード例 #1
0
        /// <summary>
        ///     Write data into the XmlWriter
        /// </summary>
        /// <param name="writer">XmlWriter to write to</param>
        public void WriteXmlString(XmlWriter writer)
        {
            writer.WriteStartElement("hotkey");

            writer.WriteStartElement("modifiers");
            writer.WriteString(Authenticator.ByteArrayToString(BitConverter.GetBytes((int)Modifiers)));
            writer.WriteEndElement();
            //
            writer.WriteStartElement("key");
            writer.WriteString(Authenticator.ByteArrayToString(BitConverter.GetBytes((ushort)Key)));
            writer.WriteEndElement();
            //
            writer.WriteStartElement("action");
            writer.WriteString(Enum.GetName(typeof(HotKeyActions), Action));
            writer.WriteEndElement();
            //
            if (string.IsNullOrEmpty(Window) == false)
            {
                writer.WriteStartElement("window");
                writer.WriteString(Window);
                writer.WriteEndElement();
            }

            //
            if (string.IsNullOrEmpty(Advanced) == false)
            {
                writer.WriteStartElement("advanced");
                writer.WriteString(Advanced);
                writer.WriteEndElement();
            }

            writer.WriteEndElement();
        }
コード例 #2
0
        /// <summary>
        /// Create a random Device ID string for Enrolling
        /// </summary>
        /// <returns>Random string</returns>
        private static string BuildRandomId()
        {
            using (var sha1 = new SHA1Managed())
            {
                RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
                byte[] buffer = new byte[4];
                random.GetBytes(buffer);

                byte[] hash = sha1.ComputeHash(buffer);
                return("android:" + Authenticator.ByteArrayToString(hash));
            }
        }
コード例 #3
0
ファイル: HotKeySequence.cs プロジェクト: ttodua/winauth
        /// <summary>
        /// Write data into the XmlWriter
        /// </summary>
        /// <param name="writer">XmlWriter to write to</param>
        public void WriteXmlString(XmlWriter writer)
        {
            writer.WriteStartElement("autologin");

            writer.WriteStartElement("modifiers");
            writer.WriteString(Authenticator.ByteArrayToString(BitConverter.GetBytes((int)Modifiers)));
            writer.WriteEndElement();
            //
            writer.WriteStartElement("hotkey");
            writer.WriteString(Authenticator.ByteArrayToString(BitConverter.GetBytes((ushort)HotKey)));
            writer.WriteEndElement();
            //
            writer.WriteStartElement("windowtitle");
            writer.WriteCData(WindowTitle ?? string.Empty);
            writer.WriteEndElement();
            //
            writer.WriteStartElement("processname");
            writer.WriteCData(ProcessName ?? string.Empty);
            writer.WriteEndElement();
            //
            writer.WriteStartElement("windowtitleregex");
            writer.WriteValue(WindowTitleRegex);
            writer.WriteEndElement();
            //
            writer.WriteStartElement("advanced");
            writer.WriteValue(Advanced);
            writer.WriteEndElement();
            //
            writer.WriteStartElement("script");
            string script = AdvancedScript.Replace("\n", string.Empty);

            writer.WriteCData(script);
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
コード例 #4
0
ファイル: WinAuthConfig.cs プロジェクト: Lanservery/winauth
        /// <summary>
        /// Write the data as xml into an XmlWriter
        /// </summary>
        /// <param name="writer">XmlWriter to write config</param>
        public void WriteXmlString(XmlWriter writer, bool includeFilename = false, bool includeSettings = true)
        {
            writer.WriteStartDocument(true);
            //
            if (includeFilename == true && string.IsNullOrEmpty(this.Filename) == false)
            {
                writer.WriteComment(this.Filename);
            }
            //
            writer.WriteStartElement("WinAuth");
            writer.WriteAttributeString("version", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(2));
            //
            writer.WriteStartElement("alwaysontop");
            writer.WriteValue(this.AlwaysOnTop);
            writer.WriteEndElement();
            //
            writer.WriteStartElement("usetrayicon");
            writer.WriteValue(this.UseTrayIcon);
            writer.WriteEndElement();
            //
            writer.WriteStartElement("notifyaction");
            writer.WriteValue(Enum.GetName(typeof(NotifyActions), this.NotifyAction));
            writer.WriteEndElement();
            //
            writer.WriteStartElement("startwithwindows");
            writer.WriteValue(this.StartWithWindows);
            writer.WriteEndElement();
            //
            writer.WriteStartElement("autosize");
            writer.WriteValue(this.AutoSize);
            writer.WriteEndElement();
            //
            if (this.Position.IsEmpty == false)
            {
                writer.WriteStartElement("left");
                writer.WriteValue(this.Position.X);
                writer.WriteEndElement();
                writer.WriteStartElement("top");
                writer.WriteValue(this.Position.Y);
                writer.WriteEndElement();
            }
            //
            writer.WriteStartElement("width");
            writer.WriteValue(this.Width);
            writer.WriteEndElement();
            //
            writer.WriteStartElement("height");
            writer.WriteValue(this.Height);
            writer.WriteEndElement();
            //
            if (string.IsNullOrEmpty(this.ShadowType) == false)
            {
                writer.WriteStartElement("shadowtype");
                writer.WriteValue(this.ShadowType);
                writer.WriteEndElement();
            }
            //
            if (string.IsNullOrEmpty(this.PGPKey) == false)
            {
                writer.WriteStartElement("pgpkey");
                writer.WriteCData(this.PGPKey);
                writer.WriteEndElement();
            }

            if (PasswordType != Authenticator.PasswordTypes.None)
            {
                writer.WriteStartElement("data");

                StringBuilder encryptedTypes = new StringBuilder();
                if ((PasswordType & Authenticator.PasswordTypes.Explicit) != 0)
                {
                    encryptedTypes.Append("y");
                }
                if ((PasswordType & Authenticator.PasswordTypes.User) != 0)
                {
                    encryptedTypes.Append("u");
                }
                if ((PasswordType & Authenticator.PasswordTypes.Machine) != 0)
                {
                    encryptedTypes.Append("m");
                }
                if ((PasswordType & Authenticator.PasswordTypes.YubiKeySlot1) != 0)
                {
                    encryptedTypes.Append("a");
                }
                if ((PasswordType & Authenticator.PasswordTypes.YubiKeySlot2) != 0)
                {
                    encryptedTypes.Append("b");
                }
                writer.WriteAttributeString("encrypted", encryptedTypes.ToString());

                byte[] data;
                using (MemoryStream ms = new MemoryStream())
                {
                    XmlWriterSettings settings = new XmlWriterSettings();
                    settings.Indent   = true;
                    settings.Encoding = Encoding.UTF8;
                    using (XmlWriter encryptedwriter = XmlWriter.Create(ms, settings))
                    {
                        encryptedwriter.WriteStartElement("config");
                        foreach (WinAuthAuthenticator wa in this)
                        {
                            wa.WriteXmlString(encryptedwriter);
                        }
                        encryptedwriter.WriteEndElement();
                    }

                    data = ms.ToArray();
                }

                using (var hasher = new MD5CryptoServiceProvider())
                {
                    string encdata = Authenticator.EncryptSequence(Authenticator.ByteArrayToString(data), PasswordType, Password, this.Yubi);
                    string enchash = Authenticator.ByteArrayToString(hasher.ComputeHash(Authenticator.StringToByteArray(encdata)));
                    writer.WriteAttributeString("md5", enchash);
                    writer.WriteString(encdata);
                }

                writer.WriteEndElement();
            }
            else
            {
                foreach (WinAuthAuthenticator wa in this)
                {
                    wa.WriteXmlString(writer);
                }
            }

            if (includeSettings == true && _settings.Count != 0)
            {
                XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
                ns.Add(string.Empty, string.Empty);
                XmlSerializer serializer = new XmlSerializer(typeof(setting[]), new XmlRootAttribute()
                {
                    ElementName = "settings"
                });
                serializer.Serialize(writer, _settings.Select(e => new setting {
                    Key = e.Key, Value = e.Value
                }).ToArray(), ns);
            }

            // close WinAuth
            writer.WriteEndElement();

            // end document
            writer.WriteEndDocument();
        }
コード例 #5
0
        /// <summary>
        /// Enroll the authenticator with the server
        /// </summary>
        public bool Enroll(EnrollState state)
        {
            // clear error
            state.Error = null;

            try
            {
                var    data    = new NameValueCollection();
                var    cookies = state.Cookies = state.Cookies ?? new CookieContainer();
                string response;

                if (string.IsNullOrEmpty(state.OAuthToken) == true)
                {
                    // get session
                    if (cookies.Count == 0)
                    {
                        cookies.Add(new Uri(COMMUNITY_BASE + "/"), new Cookie("mobileClientVersion", "3067969+%282.1.3%29"));
                        cookies.Add(new Uri(COMMUNITY_BASE + "/"), new Cookie("mobileClient", "android"));
                        cookies.Add(new Uri(COMMUNITY_BASE + "/"), new Cookie("steamid", ""));
                        cookies.Add(new Uri(COMMUNITY_BASE + "/"), new Cookie("steamLogin", ""));
                        cookies.Add(new Uri(COMMUNITY_BASE + "/"), new Cookie("Steam_Language", "english"));
                        cookies.Add(new Uri(COMMUNITY_BASE + "/"), new Cookie("dob", ""));

                        NameValueCollection headers = new NameValueCollection();
                        headers.Add("X-Requested-With", "com.valvesoftware.android.steam.community");

                        response = Request("https://steamcommunity.com/mobilelogin?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client", "GET", null, cookies, headers);
                    }

                    // get the user's RSA key
                    data.Add("username", state.Username);
                    response = Request(COMMUNITY_BASE + "/mobilelogin/getrsakey", "POST", data, cookies);
                    var rsaresponse = JObject.Parse(response);
                    if (rsaresponse.SelectToken("success").Value <bool>() != true)
                    {
                        throw new InvalidEnrollResponseException("Cannot get steam information for user: "******"publickey_exp").Value <string>());
                        p.Modulus  = Authenticator.StringToByteArray(rsaresponse.SelectToken("publickey_mod").Value <string>());
                        rsa.ImportParameters(p);
                        encryptedPassword = rsa.Encrypt(passwordBytes, false);
                    }

                    // login request
                    data = new NameValueCollection();
                    data.Add("password", Convert.ToBase64String(encryptedPassword));
                    data.Add("username", state.Username);
                    data.Add("twofactorcode", "");
                    data.Add("emailauth", (state.EmailAuthText != null ? state.EmailAuthText : string.Empty));
                    data.Add("loginfriendlyname", "#login_emailauth_friendlyname_mobile");
                    data.Add("captchagid", (state.CaptchaId != null ? state.CaptchaId : "-1"));
                    data.Add("captcha_text", (state.CaptchaText != null ? state.CaptchaText : "enter above characters"));
                    data.Add("emailsteamid", (state.EmailAuthText != null ? state.SteamId ?? string.Empty : string.Empty));
                    data.Add("rsatimestamp", rsaresponse.SelectToken("timestamp").Value <string>());
                    data.Add("remember_login", "false");
                    data.Add("oauth_client_id", "DE45CD61");
                    data.Add("oauth_scope", "read_profile write_profile read_client write_client");
                    data.Add("donotache", new DateTime().ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds.ToString());
                    response = Request(COMMUNITY_BASE + "/mobilelogin/dologin/", "POST", data, cookies);
                    Dictionary <string, object> loginresponse = JsonConvert.DeserializeObject <Dictionary <string, object> >(response);

                    if (loginresponse.ContainsKey("emailsteamid") == true)
                    {
                        state.SteamId = loginresponse["emailsteamid"] as string;
                    }

                    // require captcha
                    if (loginresponse.ContainsKey("captcha_needed") == true && (bool)loginresponse["captcha_needed"] == true)
                    {
                        state.RequiresCaptcha = true;
                        state.CaptchaId       = (string)loginresponse["captcha_gid"];
                        state.CaptchaUrl      = COMMUNITY_BASE + "/public/captcha.php?gid=" + state.CaptchaId;
                    }
                    else
                    {
                        state.RequiresCaptcha = false;
                        state.CaptchaId       = null;
                        state.CaptchaUrl      = null;
                        state.CaptchaText     = null;
                    }

                    // require email auth
                    if (loginresponse.ContainsKey("emailauth_needed") == true && (bool)loginresponse["emailauth_needed"] == true)
                    {
                        if (loginresponse.ContainsKey("emaildomain") == true)
                        {
                            var emaildomain = (string)loginresponse["emaildomain"];
                            if (string.IsNullOrEmpty(emaildomain) == false)
                            {
                                state.EmailDomain = emaildomain;
                            }
                        }
                        state.RequiresEmailAuth = true;
                    }
                    else
                    {
                        state.EmailDomain       = null;
                        state.RequiresEmailAuth = false;
                    }

                    // require email auth
                    if (loginresponse.ContainsKey("requires_twofactor") == true && (bool)loginresponse["requires_twofactor"] == true)
                    {
                        state.Requires2FA = true;
                    }
                    else
                    {
                        state.Requires2FA = false;
                    }

                    // if we didn't login, return the result
                    if (loginresponse.ContainsKey("login_complete") == false || (bool)loginresponse["login_complete"] == false || loginresponse.ContainsKey("oauth") == false)
                    {
                        if (loginresponse.ContainsKey("oauth") == false)
                        {
                            state.Error = "Invalid response from Steam (No OAuth token)";
                        }
                        if (loginresponse.ContainsKey("message") == true)
                        {
                            state.Error = (string)loginresponse["message"];
                        }
                        return(false);
                    }

                    // get the OAuth token - is stringified json
                    string oauth     = (string)loginresponse["oauth"];
                    var    oauthjson = JObject.Parse(oauth);
                    state.OAuthToken = oauthjson.SelectToken("oauth_token").Value <string>();
                    if (oauthjson.SelectToken("steamid") != null)
                    {
                        state.SteamId = oauthjson.SelectToken("steamid").Value <string>();
                    }
                }

                // login to webapi
                data.Clear();
                data.Add("access_token", state.OAuthToken);
                response = Request(WEBAPI_BASE + "/ISteamWebUserPresenceOAuth/Logon/v0001", "POST", data);

                var sessionid = cookies.GetCookies(new Uri(COMMUNITY_BASE + "/"))["sessionid"].Value;

                if (state.RequiresActivation == false)
                {
                    data.Clear();
                    data.Add("op", "has_phone");
                    data.Add("arg", "null");
                    data.Add("sessionid", sessionid);

                    response = Request(COMMUNITY_BASE + "/steamguard/phoneajax", "POST", data, cookies);
                    var  jsonresponse = JObject.Parse(response);
                    bool hasPhone     = jsonresponse.SelectToken("has_phone").Value <Boolean>();
                    if (hasPhone == false)
                    {
                        state.OAuthToken    = null;                      // force new login
                        state.RequiresLogin = true;
                        state.Cookies       = null;
                        state.Error         = "Your Steam account must have a SMS-capable phone number attached. Go into Account Details of the Steam client or Steam website and click Add a Phone Number.";
                        return(false);
                    }

                    //response = Request(COMMUNITY_BASE + "/steamguard/phone_checksms?bForTwoFactor=1&bRevoke2fOnCancel=", "GET", null, cookies);

                    // add a new authenticator
                    data.Clear();
                    string deviceId = BuildRandomId();
                    data.Add("access_token", state.OAuthToken);
                    data.Add("steamid", state.SteamId);
                    data.Add("authenticator_type", "1");
                    data.Add("device_identifier", deviceId);
                    data.Add("sms_phone_id", "1");
                    response = Request(WEBAPI_BASE + "/ITwoFactorService/AddAuthenticator/v0001", "POST", data);
                    var tfaresponse = JObject.Parse(response);
                    if (response.IndexOf("status") == -1 && tfaresponse.SelectToken("response.status").Value <int>() == 84)
                    {
                        // invalid response
                        state.OAuthToken    = null;                      // force new login
                        state.RequiresLogin = true;
                        state.Cookies       = null;
                        state.Error         = "Unable to send SMS. Check your phone is registered on your Steam account.";
                        return(false);
                    }
                    if (response.IndexOf("shared_secret") == -1)
                    {
                        // invalid response
                        state.OAuthToken    = null;                      // force new login
                        state.RequiresLogin = true;
                        state.Cookies       = null;
                        state.Error         = "Invalid response from Steam: " + response;
                        return(false);
                    }

                    // save data into this authenticator
                    var secret = tfaresponse.SelectToken("response.shared_secret").Value <string>();
                    this.SecretKey       = Convert.FromBase64String(secret);
                    this.Serial          = tfaresponse.SelectToken("response.serial_number").Value <string>();
                    this.DeviceId        = deviceId;
                    state.RevocationCode = tfaresponse.SelectToken("response.revocation_code").Value <string>();

                    // add the steamid into the data
                    var steamdata = JObject.Parse(tfaresponse.SelectToken("response").ToString());
                    if (steamdata.SelectToken("steamid") == null)
                    {
                        steamdata.Add("steamid", state.SteamId);
                    }
                    if (steamdata.SelectToken("steamguard_scheme") == null)
                    {
                        steamdata.Add("steamguard_scheme", "2");
                    }
                    this.SteamData = steamdata.ToString(Newtonsoft.Json.Formatting.None);

                    // calculate server drift
                    long servertime = tfaresponse.SelectToken("response.server_time").Value <long>() * 1000;
                    ServerTimeDiff = servertime - CurrentTime;
                    LastServerTime = DateTime.Now.Ticks;

                    state.RequiresActivation = true;

                    return(false);
                }

                // finalize adding the authenticator
                data.Clear();
                data.Add("access_token", state.OAuthToken);
                data.Add("steamid", state.SteamId);
                data.Add("activation_code", state.ActivationCode);

                // try and authorise
                var retries = 0;
                while (state.RequiresActivation == true && retries < ENROLL_ACTIVATE_RETRIES)
                {
                    data.Add("authenticator_code", this.CalculateCode(false));
                    data.Add("authenticator_time", this.ServerTime.ToString());
                    response = Request(WEBAPI_BASE + "/ITwoFactorService/FinalizeAddAuthenticator/v0001", "POST", data);
                    var finalizeresponse = JObject.Parse(response);
                    if (response.IndexOf("status") != -1 && finalizeresponse.SelectToken("response.status").Value <int>() == INVALID_ACTIVATION_CODE)
                    {
                        state.Error = "Invalid activation code";
                        return(false);
                    }

                    // reset our time
                    if (response.IndexOf("server_time") != -1)
                    {
                        long servertime = finalizeresponse.SelectToken("response.server_time").Value <long>() * 1000;
                        ServerTimeDiff = servertime - CurrentTime;
                        LastServerTime = DateTime.Now.Ticks;
                    }

                    // check success
                    if (finalizeresponse.SelectToken("response.success").Value <bool>() == true)
                    {
                        if (response.IndexOf("want_more") != -1 && finalizeresponse.SelectToken("response.want_more").Value <bool>() == true)
                        {
                            ServerTimeDiff += 30000L;
                            retries++;
                            continue;
                        }
                        state.RequiresActivation = false;
                        break;
                    }

                    ServerTimeDiff += 30000L;
                    retries++;
                }
                if (state.RequiresActivation == true)
                {
                    state.Error = "There was a problem activating. There might be an issue with the Steam servers. Please try again later.";
                    return(false);
                }

                // mark and successful and return key
                state.Success   = true;
                state.SecretKey = Authenticator.ByteArrayToString(this.SecretKey);

                // send confirmation email
                data.Clear();
                data.Add("access_token", state.OAuthToken);
                data.Add("steamid", state.SteamId);
                data.Add("email_type", "2");
                response = Request(WEBAPI_BASE + "/ITwoFactorService/SendEmail/v0001", "POST", data);

                return(true);
            }
            catch (UnauthorisedRequestException ex)
            {
                throw new InvalidEnrollResponseException("You are not allowed to add an authenticator. Have you enabled 'community-generated content' in Family View?", ex);
            }
            catch (InvalidRequestException ex)
            {
                throw new InvalidEnrollResponseException("Error enrolling new authenticator", ex);
            }
        }
コード例 #6
0
        /// <summary>
        /// Configure the Yubikey
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void yubiSecretUpdateButton_Click(object sender, EventArgs e)
        {
            if (yubiSecretField.Text.Trim().Length == 0)
            {
                WinAuthForm.ErrorDialog(this, "Please enter a secret phase or password");
                return;
            }

            if (WinAuthForm.ConfirmDialog(this,
                                          "This will overwrite any existing data on your YubiKey.\n\nAre you sure you want to continue?",
                                          MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2) != System.Windows.Forms.DialogResult.Yes)
            {
                return;
            }

            int  slot  = (yubiSlotToggle.Checked == true ? 2 : 1);
            bool press = yubiPressToggle.Checked;

            // bug in YubiKey 3.2.x (and below?) where using keypress doesn't always work
            // see http://forum.yubico.com/viewtopic.php?f=26&t=1571
            if (press == true &&
                (this.Yubikey.Info.Status.VersionMajor < 3 ||
                 (this.Yubikey.Info.Status.VersionMajor == 3 && this.Yubikey.Info.Status.VersionMinor <= 3)))
            {
                if (WinAuthForm.ConfirmDialog(this,
                                              "This is a known issue using \"Require button press\" with YubiKeys that have firmware version 3.3 and below. It can cause intermittent problems when reading the Challenge-Response. You can contact Yubico and may be able to get a free replacement.\n\nDo you want to continue anyway?",
                                              MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2) != System.Windows.Forms.DialogResult.Yes)
                {
                    return;
                }
            }

            // calculate the actual key. This is a byte version of the string, salt="mnemonic"(+user password TBD), PBKDF 2048 times, return 20byte/160bit key
            byte[] bytes = Encoding.UTF8.GetBytes(yubiSecretField.Text.Trim());
            string salt  = "mnemonic";

            byte[]             saltbytes = Encoding.UTF8.GetBytes(salt);
            Rfc2898DeriveBytes kg        = new Rfc2898DeriveBytes(bytes, saltbytes, YUBIKEY_PBKDF2_ITERATIONS);

            byte[] key = kg.GetBytes(YUBIKEY_PBKDF2_KEYSIZE);

            try
            {
                this.Yubikey.SetChallengeResponse(slot, key, key.Length, press);
            }
            catch (YubKeyException ex)
            {
                WinAuthForm.ErrorDialog(this, ex.Message, ex);
                return;
            }

            if (press == true)
            {
                if (WinAuthForm.ConfirmDialog(this,
                                              "Your YubiKey slot will now be verified. Please click its button when it flashes." + Environment.NewLine + Environment.NewLine + "Continue?",
                                              MessageBoxButtons.YesNo,
                                              MessageBoxIcon.Warning) != System.Windows.Forms.DialogResult.Yes)
                {
                    WinAuthForm.ErrorDialog(this, "Your YubiKey has been updated. Please verify it before continuing.");
                    return;
                }
            }

            // perform the test encryption/decryption using the yubi
            try
            {
                string challenge = "WinAuth";
                string plain     = Authenticator.ByteArrayToString(Encoding.ASCII.GetBytes(challenge));
                Authenticator.PasswordTypes passwordType = (slot == 1 ? Authenticator.PasswordTypes.YubiKeySlot1 : Authenticator.PasswordTypes.YubiKeySlot2);
                string encrypted = Authenticator.EncryptSequence(plain, passwordType, null, this.Yubikey);
                plain = Authenticator.DecryptSequence(encrypted, passwordType, null, this.Yubikey);
                string response = Encoding.ASCII.GetString(Authenticator.StringToByteArray(plain));
                if (challenge != response)
                {
                    throw new ApplicationException("verification failed");
                }
            }
            catch (ApplicationException ex)
            {
                WinAuthForm.ErrorDialog(this, "The YubiKey test failed. Please try configuring it again or doing it manually. (" + ex.Message + ")");
                return;
            }

            YubikeySlot = slot;

            WinAuthForm.ErrorDialog(this, "Your YubiKey has been successfully updated.");
        }
コード例 #7
0
        /// <summary>
        /// Enroll the authenticator with the server
        /// </summary>
        public bool Enroll(EnrollState state)
        {
            // clear error
            state.Error = null;

            try
            {
                var    data    = new NameValueCollection();
                var    cookies = state.Cookies = state.Cookies ?? new CookieContainer();
                string response;

                if (string.IsNullOrEmpty(state.OAuthToken) == true)
                {
                    // get session
                    response = Request(COMMUNITY_BASE + "/login/home?goto=0", "GET", null, cookies);

                    // get the user's RSA key
                    data.Add("username", state.Username);
                    response = Request(COMMUNITY_BASE + "/login/getrsakey", "POST", data, cookies);
                    var rsaresponse = JObject.Parse(response);
                    if (rsaresponse.SelectToken("success").Value <bool>() != true)
                    {
                        throw new InvalidEnrollResponseException("Cannot get steam information for user: "******"steamid").Value <string>();

                    // encrypt password with RSA key
                    RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
                    byte[] encryptedPassword;
                    using (var rsa = new RSACryptoServiceProvider())
                    {
                        var passwordBytes = Encoding.ASCII.GetBytes(state.Password);
                        var p             = rsa.ExportParameters(false);
                        p.Exponent = Authenticator.StringToByteArray(rsaresponse.SelectToken("publickey_exp").Value <string>());
                        p.Modulus  = Authenticator.StringToByteArray(rsaresponse.SelectToken("publickey_mod").Value <string>());
                        rsa.ImportParameters(p);
                        encryptedPassword = rsa.Encrypt(passwordBytes, false);
                    }

                    // login request
                    data = new NameValueCollection();
                    data.Add("password", Convert.ToBase64String(encryptedPassword));
                    data.Add("username", state.Username);
                    data.Add("twofactorcode", "");
                    data.Add("emailauth", (state.EmailAuthText != null ? state.EmailAuthText : string.Empty));
                    data.Add("loginfriendlyname", "#login_emailauth_friendlyname_mobile");
                    data.Add("captchagid", (state.CaptchaId != null ? state.CaptchaId : "-1"));
                    data.Add("captcha_text", (state.CaptchaText != null ? state.CaptchaText : "enter above characters"));
                    data.Add("emailsteamid", (state.EmailAuthText != null ? state.SteamId : string.Empty));
                    data.Add("rsatimestamp", rsaresponse.SelectToken("timestamp").Value <string>());
                    data.Add("remember_login", "false");
                    data.Add("oauth_client_id", "DE45CD61");
                    data.Add("oauth_scope", "read_profile write_profile read_client write_client");
                    data.Add("donotache", new DateTime().ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds.ToString());
                    response = Request(COMMUNITY_BASE + "/mobilelogin/dologin/", "POST", data, cookies);
                    Dictionary <string, object> loginresponse = JsonConvert.DeserializeObject <Dictionary <string, object> >(response);

                    // require captcha
                    if (loginresponse.ContainsKey("captcha_needed") == true && (bool)loginresponse["captcha_needed"] == true)
                    {
                        state.RequiresCaptcha = true;
                        state.CaptchaId       = (string)loginresponse["captcha_gid"];
                        state.CaptchaUrl      = COMMUNITY_BASE + "/public/captcha.php?gid=" + state.CaptchaId;
                    }
                    else
                    {
                        state.RequiresCaptcha = false;
                        state.CaptchaId       = null;
                        state.CaptchaUrl      = null;
                        state.CaptchaText     = null;
                    }

                    // require email auth
                    if (loginresponse.ContainsKey("emailauth_needed") == true && (bool)loginresponse["emailauth_needed"] == true)
                    {
                        if (loginresponse.ContainsKey("emaildomain") == true)
                        {
                            var emaildomain = (string)loginresponse["emaildomain"];
                            if (string.IsNullOrEmpty(emaildomain) == false)
                            {
                                state.EmailDomain = emaildomain;
                            }
                        }
                        state.RequiresEmailAuth = true;
                    }
                    else
                    {
                        state.EmailDomain       = null;
                        state.RequiresEmailAuth = false;
                    }

                    // require email auth
                    if (loginresponse.ContainsKey("requires_twofactor") == true && (bool)loginresponse["requires_twofactor"] == true)
                    {
                        state.Requires2FA = true;
                    }
                    else
                    {
                        state.Requires2FA = false;
                    }

                    // if we didn't login, return the result
                    if (loginresponse.ContainsKey("login_complete") == false || (bool)loginresponse["login_complete"] == false || loginresponse.ContainsKey("oauth") == false)
                    {
                        if (loginresponse.ContainsKey("oauth") == false)
                        {
                            state.Error = "No OAuth token in response";
                        }
                        if (loginresponse.ContainsKey("message") == true)
                        {
                            state.Error = (string)loginresponse["message"];
                        }
                        return(false);
                    }

                    // get the OAuth token - is stringified json
                    string oauth     = (string)loginresponse["oauth"];
                    var    oauthjson = JObject.Parse(oauth);
                    state.OAuthToken = oauthjson.SelectToken("oauth_token").Value <string>();
                }

                // login to webapi
                data.Clear();
                data.Add("access_token", state.OAuthToken);
                response = Request(WEBAPI_BASE + "/ISteamWebUserPresenceOAuth/Logon/v0001", "POST", data);

                if (state.RequiresActivation == false)
                {
                    // add a new authenticator
                    data.Clear();
                    string deviceId = BuildRandomId();
                    data.Add("access_token", state.OAuthToken);
                    data.Add("steamid", state.SteamId);
                    data.Add("authenticator_type", "1");
                    data.Add("device_identifier", deviceId);
                    response = Request(WEBAPI_BASE + "/ITwoFactorService/AddAuthenticator/v0001", "POST", data);
                    var tfaresponse = JObject.Parse(response);
                    if (response.IndexOf("revocation_code") == -1)
                    {
                        // invalid response
                        state.OAuthToken    = null;                      // force new login
                        state.RequiresLogin = true;
                        state.Cookies       = null;
                        state.Error         = "Invalid response from Steam: " + response;
                        return(false);
                    }

                    // save data into this authenticator
                    var secret = tfaresponse.SelectToken("response.shared_secret").Value <string>();
                    this.SecretKey      = Convert.FromBase64String(secret);
                    this.Serial         = tfaresponse.SelectToken("response.serial_number").Value <string>();
                    this.DeviceId       = deviceId;
                    this.RevocationCode = state.RevocationCode = tfaresponse.SelectToken("response.revocation_code").Value <string>();

                    // calculate server drift
                    long servertime = tfaresponse.SelectToken("response.server_time").Value <long>() * 1000;
                    ServerTimeDiff = servertime - CurrentTime;
                    LastServerTime = DateTime.Now.Ticks;

                    // send authorisation email
                    data.Clear();
                    data.Add("access_token", state.OAuthToken);
                    data.Add("steamid", state.SteamId);
                    data.Add("email_type", "1");
                    data.Add("include_activation", "1");
                    response = Request(WEBAPI_BASE + "/ITwoFactorService/SendEmail/v0001", "POST", data);

                    state.RequiresActivation = true;

                    return(false);
                }

                // finalize adding the authenticator
                data.Clear();
                data.Add("access_token", state.OAuthToken);
                data.Add("steamid", state.SteamId);
                data.Add("activation_code", state.ActivationCode);

                // try and authorise
                ServerTimeDiff -= 40000;                 // start at previous interval
                var retries = 0;
                while (state.RequiresActivation == true && retries < ENROLL_ACTIVATE_RETRIES)
                {
                    data.Add("authenticator_code", this.CalculateCode(false));
                    data.Add("authenticator_time", this.ServerTime.ToString());
                    response = Request(WEBAPI_BASE + "/ITwoFactorService/FinalizeAddAuthenticator/v0001", "POST", data);
                    var finalizeresponse = JObject.Parse(response);
                    if (response.IndexOf("status") != -1 && finalizeresponse.SelectToken("response.status").Value <int>() == INVALID_ACTIVATION_CODE)
                    {
                        state.Error = "Invalid activation code";
                        return(false);
                    }

                    // reset our time
                    if (response.IndexOf("server_time") != -1)
                    {
                        long servertime = finalizeresponse.SelectToken("response.server_time").Value <long>() * 1000;
                        ServerTimeDiff = servertime - CurrentTime;
                        LastServerTime = DateTime.Now.Ticks;
                    }

                    // check success
                    if (finalizeresponse.SelectToken("response.success").Value <bool>() == true)
                    {
                        if (response.IndexOf("want_more") != -1 && finalizeresponse.SelectToken("response.want_more").Value <bool>() == true)
                        {
                            ServerTimeDiff += 30000L;
                            retries++;
                            continue;
                        }
                        state.RequiresActivation = false;
                        break;
                    }

                    ServerTimeDiff += 30000L;
                    retries++;
                }
                if (state.RequiresActivation == true)
                {
                    state.Error = "There was a problem activating. There might be an issue with the Steam servers. Please try again later.";
                    return(false);
                }

                // mark and successful and return key
                state.Success   = true;
                state.SecretKey = Authenticator.ByteArrayToString(this.SecretKey);

                // send confirmation email
                data.Clear();
                data.Add("access_token", state.OAuthToken);
                data.Add("steamid", state.SteamId);
                data.Add("email_type", "2");
                response = Request(WEBAPI_BASE + "/ITwoFactorService/SendEmail/v0001", "POST", data);

                return(true);
            }
            catch (InvalidRequestException ex)
            {
                throw new InvalidEnrollResponseException("Error enrolling new authenticator", ex);
            }
        }