/// <summary> /// Form loaded event /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ShowSecretKeyForm_Load(object sender, EventArgs e) { secretKeyField.SecretMode = true; var key = Base32.getInstance().Encode(CurrentAuthenticator.AuthenticatorData.SecretKey); secretKeyField.Text = Regex.Replace(key, ".{3}", "$0 ").Trim(); var type = CurrentAuthenticator.AuthenticatorData is HOTPAuthenticator ? "hotp" : "totp"; var counter = CurrentAuthenticator.AuthenticatorData is HOTPAuthenticator ? ((HOTPAuthenticator)CurrentAuthenticator.AuthenticatorData).Counter : 0; var issuer = CurrentAuthenticator.AuthenticatorData.Issuer; //string url = "otpauth://" + type + "/" + WinAuthHelper.HtmlEncode(CurrentAuthenticator.Name) // + "?secret=" + key // + "&digits=" + CurrentAuthenticator.AuthenticatorData.CodeDigits // + (counter != 0 ? "&counter=" + counter : string.Empty) // + (string.IsNullOrEmpty(issuer) == false ? "&issuer=" + WinAuthHelper.HtmlEncode(issuer) : string.Empty); var url = CurrentAuthenticator.ToUrl(true); var writer = new BarcodeWriter(); writer.Format = BarcodeFormat.QR_CODE; writer.Options = new EncodingOptions { Width = qrImage.Width, Height = qrImage.Height }; qrImage.Image = writer.Write(url); }
/// <summary> /// Create a KeyUriFormat compatible URL /// See https://code.google.com/p/google-authenticator/wiki/KeyUriFormat /// </summary> /// <returns>string</returns> public virtual string ToUrl() { string type = "totp"; string extraparams = string.Empty; Match match; string issuer = this.AuthenticatorData.Issuer; string label = this.Name; if (string.IsNullOrEmpty(issuer) == true && (match = Regex.Match(label, @"^([^\(]+)\s+\((.*?)\)(.*)")).Success == true) { issuer = match.Groups[1].Value; label = match.Groups[2].Value + match.Groups[3].Value; } if (string.IsNullOrEmpty(issuer) == false && (match = Regex.Match(label, @"^" + issuer + @"\s+\((.*?)\)(.*)")).Success == true) { label = match.Groups[1].Value + match.Groups[2].Value; } if (string.IsNullOrEmpty(issuer) == false) { extraparams += "&issuer=" + HttpUtility.UrlEncode(issuer); } if (this.AuthenticatorData is BattleNetAuthenticator) { extraparams += "&serial=" + HttpUtility.UrlEncode(((BattleNetAuthenticator)this.AuthenticatorData).Serial.Replace("-", "")); } else if (this.AuthenticatorData is HOTPAuthenticator) { type = "hotp"; extraparams += "&counter=" + ((HOTPAuthenticator)this.AuthenticatorData).Counter; } string secret = HttpUtility.UrlEncode(Base32.getInstance().Encode(this.AuthenticatorData.SecretKey)); // add the skin if (string.IsNullOrEmpty(this.Skin) == false) { if (this.Skin.StartsWith("base64:") == true) { byte[] bytes = Convert.FromBase64String(this.Skin.Substring(7)); string icon32 = Base32.getInstance().Encode(bytes); extraparams += "&icon=" + HttpUtility.UrlEncode("base64:" + icon32); } else { extraparams += "&icon=" + HttpUtility.UrlEncode(this.Skin.Replace("Icon.png", "")); } } var url = string.Format("otpauth://" + type + "/{0}?secret={1}&digits={2}{3}", (string.IsNullOrEmpty(issuer) == false ? issuer + ":" + HttpUtility.UrlEncode(label) : HttpUtility.UrlEncode(label)), secret, this.AuthenticatorData.CodeDigits, extraparams); return(url); }
/// <summary> /// Toggle the secret mode to allow copy /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void allowCopyCheckBox_CheckedChanged(object sender, EventArgs e) { secretKeyField.SecretMode = !allowCopyCheckBox.Checked; var key = Base32.getInstance().Encode(CurrentAuthenticator.AuthenticatorData.SecretKey); if (secretKeyField.SecretMode) { secretKeyField.Text = Regex.Replace(key, ".{3}", "$0 ").Trim(); } else { secretKeyField.Text = key; } }
/// <summary> /// Create a KeyUriFormat compatible URL /// See https://code.google.com/p/google-authenticator/wiki/KeyUriFormat /// </summary> /// <returns>string</returns> public virtual string ToUrl() { string type = "totp"; string extraparams = string.Empty; string issuer = this.AuthenticatorData.Issuer; if (string.IsNullOrEmpty(issuer) == false) { extraparams += "&issuer=" + HttpUtility.UrlEncode(issuer); } if (this.AuthenticatorData is BattleNetAuthenticator) { extraparams += "&serial=" + HttpUtility.UrlEncode(((BattleNetAuthenticator)this.AuthenticatorData).Serial.Replace("-", "")); } else if (this.AuthenticatorData is HOTPAuthenticator) { type = "hotp"; extraparams += "&counter=" + ((HOTPAuthenticator)this.AuthenticatorData).Counter; } string label = HttpUtility.UrlEncode(this.Name); string secret = HttpUtility.UrlEncode(Base32.getInstance().Encode(this.AuthenticatorData.SecretKey)); // add the skin if (string.IsNullOrEmpty(this.Skin) == false) { if (this.Skin.StartsWith("base64:") == true) { byte[] bytes = Convert.FromBase64String(this.Skin.Substring(7)); string icon32 = Base32.getInstance().Encode(bytes); extraparams += "&icon=" + HttpUtility.UrlEncode("base64:" + icon32); } else { extraparams += "&icon=" + HttpUtility.UrlEncode(this.Skin.Replace("Icon.png", "")); } } var url = string.Format("otpauth://" + type + "/{0}?secret={1}&digits={2}{3}", (string.IsNullOrEmpty(issuer) == false ? issuer + ":" + label : label), secret, this.AuthenticatorData.CodeDigits, extraparams); return(url); }
/// <summary> /// Verify and create the authenticator if needed /// </summary> /// <returns>true is successful</returns> private bool verifyAuthenticator(string privatekey) { if (string.IsNullOrEmpty(privatekey) == true) { return(false); } this.Authenticator.Name = nameField.Text; int digits = (this.Authenticator.AuthenticatorData != null ? this.Authenticator.AuthenticatorData.CodeDigits : GoogleAuthenticator.DEFAULT_CODE_DIGITS); if (string.IsNullOrEmpty(digitsField.Text) == true || int.TryParse(digitsField.Text, out digits) == false || digits <= 0) { return(false); } WinAuth.Authenticator.HMACTypes hmac = WinAuth.Authenticator.HMACTypes.SHA1; #if NETFX_3 try { hmac = (WinAuth.Authenticator.HMACTypes)Enum.Parse(typeof(WinAuth.Authenticator.HMACTypes), (string)hashField.SelectedItem, true); } catch (Exception) { } #else Enum.TryParse <WinAuth.Authenticator.HMACTypes>((string)hashField.SelectedItem, out hmac); #endif string authtype = timeBasedRadio.Checked == true ? TOTP : HOTP; int period = 0; if (string.IsNullOrEmpty(intervalField.Text) == true || int.TryParse(intervalField.Text, out period) == false || period <= 0) { return(false); } long counter = 0; // if this is a URL, pull it down Uri uri; Match match; if (Regex.IsMatch(privatekey, "https?://.*") == true && Uri.TryCreate(privatekey, UriKind.Absolute, out uri) == true) { try { var request = (HttpWebRequest)WebRequest.Create(uri); request.AllowAutoRedirect = true; request.Timeout = 20000; request.UserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)"; using (var response = (HttpWebResponse)request.GetResponse()) { if (response.StatusCode == HttpStatusCode.OK && response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) == true) { using (Bitmap bitmap = (Bitmap)Bitmap.FromStream(response.GetResponseStream())) { IBarcodeReader reader = new BarcodeReader(); var result = reader.Decode(bitmap); if (result != null) { privatekey = HttpUtility.UrlDecode(result.Text); } } } } } catch (Exception ex) { WinAuthForm.ErrorDialog(this.Owner, "Cannot load QR code image from " + privatekey, ex); return(false); } } else if ((match = Regex.Match(privatekey, @"data:image/([^;]+);base64,(.*)", RegexOptions.IgnoreCase)).Success == true) { byte[] imagedata = Convert.FromBase64String(match.Groups[2].Value); using (MemoryStream ms = new MemoryStream(imagedata)) { using (Bitmap bitmap = (Bitmap)Bitmap.FromStream(ms)) { IBarcodeReader reader = new BarcodeReader(); var result = reader.Decode(bitmap); if (result != null) { privatekey = HttpUtility.UrlDecode(result.Text); } } } } else if (IsValidFile(privatekey) == true) { // assume this is the image file using (Bitmap bitmap = (Bitmap)Bitmap.FromFile(privatekey)) { IBarcodeReader reader = new BarcodeReader(); var result = reader.Decode(bitmap); if (result != null) { privatekey = result.Text; } } } string issuer = null; string serial = null; // check for otpauth://, e.g. "otpauth://totp/[email protected]?secret=IHZJDKAEEC774BMUK3GX6SA" match = Regex.Match(privatekey, @"otpauth://([^/]+)/([^?]+)\?(.*)", RegexOptions.IgnoreCase); if (match.Success == true) { authtype = match.Groups[1].Value.ToLower(); string label = match.Groups[2].Value; int p = label.IndexOf(":"); if (p != -1) { issuer = label.Substring(0, p); label = label.Substring(p + 1); } NameValueCollection qs = WinAuthHelper.ParseQueryString(match.Groups[3].Value); privatekey = qs["secret"] ?? privatekey; int querydigits; if (int.TryParse(qs["digits"], out querydigits) && querydigits != 0) { digits = querydigits; } if (qs["counter"] != null) { long.TryParse(qs["counter"], out counter); } issuer = qs["issuer"]; if (string.IsNullOrEmpty(issuer) == false) { label = issuer + (string.IsNullOrEmpty(label) == false ? " (" + label + ")" : string.Empty); } serial = qs["serial"]; if (string.IsNullOrEmpty(label) == false) { this.Authenticator.Name = this.nameField.Text = label; } string periods = qs["period"]; if (string.IsNullOrEmpty(periods) == false) { int.TryParse(periods, out period); } if (qs["algorithm"] != null) { #if NETFX_3 try { hmac = (WinAuth.Authenticator.HMACTypes)Enum.Parse(typeof(WinAuth.Authenticator.HMACTypes), qs["algorithm"], true); hashField.SelectedItem = hmac.ToString(); } catch (Exception) { } #else if (Enum.TryParse <WinAuth.Authenticator.HMACTypes>(qs["algorithm"], true, out hmac) == true) { hashField.SelectedItem = hmac.ToString(); } #endif } } // just get the hex chars privatekey = Regex.Replace(privatekey, @"[^0-9a-z]", "", RegexOptions.IgnoreCase); if (privatekey.Length == 0) { WinAuthForm.ErrorDialog(this.Owner, "The secret code is not valid"); return(false); } try { Authenticator auth; if (authtype == TOTP) { if (string.Compare(issuer, "BattleNet", true) == 0) { if (string.IsNullOrEmpty(serial) == true) { throw new ApplicationException("Battle.net Authenticator does not have a serial"); } serial = serial.ToUpper(); if (Regex.IsMatch(serial, @"^[A-Z]{2}-?[\d]{4}-?[\d]{4}-?[\d]{4}$") == false) { throw new ApplicationException("Invalid serial for Battle.net Authenticator"); } auth = new BattleNetAuthenticator(); ((BattleNetAuthenticator)auth).SecretKey = Base32.getInstance().Decode(privatekey); ((BattleNetAuthenticator)auth).Serial = serial; issuer = string.Empty; } else if (issuer == "Steam") { auth = new SteamAuthenticator(); ((SteamAuthenticator)auth).SecretKey = Base32.getInstance().Decode(privatekey); ((SteamAuthenticator)auth).Serial = string.Empty; ((SteamAuthenticator)auth).DeviceId = string.Empty; //((SteamAuthenticator)auth).RevocationCode = string.Empty; ((SteamAuthenticator)auth).SteamData = string.Empty; this.Authenticator.Skin = null; issuer = string.Empty; } else { auth = new GoogleAuthenticator(); ((GoogleAuthenticator)auth).Enroll(privatekey); } timer.Enabled = true; codeProgress.Visible = true; timeBasedRadio.Checked = true; } else if (authtype == HOTP) { auth = new HOTPAuthenticator(); if (counterField.Text.Trim().Length != 0) { long.TryParse(counterField.Text.Trim(), out counter); } ((HOTPAuthenticator)auth).Enroll(privatekey, counter); // start with the next code timer.Enabled = false; codeProgress.Visible = false; counterBasedRadio.Checked = true; } else { WinAuthForm.ErrorDialog(this.Owner, "Only TOTP or HOTP authenticators are supported"); return(false); } auth.HMACType = hmac; auth.CodeDigits = digits; auth.Period = period; this.Authenticator.AuthenticatorData = auth; if (digits > 5) { codeField.SpaceOut = digits / 2; } else { codeField.SpaceOut = 0; } //string key = Base32.getInstance().Encode(this.Authenticator.AuthenticatorData.SecretKey); this.codeField.Text = auth.CurrentCode; codeProgress.Maximum = period; if (!(auth is HOTPAuthenticator) && auth.ServerTimeDiff == 0L && SyncErrorWarned == false) { SyncErrorWarned = true; MessageBox.Show(this, string.Format(strings.AuthenticatorSyncError, "Google"), WinAuthMain.APPLICATION_TITLE, MessageBoxButtons.OK, MessageBoxIcon.Warning); } } catch (Exception irre) { WinAuthForm.ErrorDialog(this.Owner, "Unable to create the authenticator. The secret code is probably invalid.", irre); return(false); } return(true); }
/// <summary> /// Verify and create the authenticator if needed /// </summary> /// <returns>true is successful</returns> private bool verifyAuthenticator(string privatekey) { if (string.IsNullOrEmpty(privatekey)) { return(false); } Authenticator.Name = nameField.Text; var authtype = "totp"; // if this is a URL, pull it down Uri uri; Match match; if (Regex.IsMatch(privatekey, "https?://.*") && Uri.TryCreate(privatekey, UriKind.Absolute, out uri)) { try { var request = (HttpWebRequest)WebRequest.Create(uri); request.AllowAutoRedirect = true; request.Timeout = 20000; request.UserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)"; using (var response = (HttpWebResponse)request.GetResponse()) { if (response.StatusCode == HttpStatusCode.OK && response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) { using (var bitmap = (Bitmap)Image.FromStream(response.GetResponseStream())) { IBarcodeReader reader = new BarcodeReader(); var result = reader.Decode(bitmap); if (result != null) { privatekey = HttpUtility.UrlDecode(result.Text); } } } } } catch (Exception ex) { WinAuthForm.ErrorDialog(Owner, "Cannot load QR code image from " + privatekey, ex); return(false); } } else if ((match = Regex.Match(privatekey, @"data:image/([^;]+);base64,(.*)", RegexOptions.IgnoreCase)) .Success) { var imagedata = Convert.FromBase64String(match.Groups[2].Value); using (var ms = new MemoryStream(imagedata)) { using (var bitmap = (Bitmap)Image.FromStream(ms)) { IBarcodeReader reader = new BarcodeReader(); var result = reader.Decode(bitmap); if (result != null) { privatekey = HttpUtility.UrlDecode(result.Text); } } } } else if (IsValidFile(privatekey)) { // assume this is the image file using (var bitmap = (Bitmap)Image.FromFile(privatekey)) { IBarcodeReader reader = new BarcodeReader(); var result = reader.Decode(bitmap); if (result != null) { privatekey = result.Text; } } } // check for otpauth://, e.g. "otpauth://totp/[email protected]?secret=IHZJDKAEEC774BMUK3GX6SA" match = Regex.Match(privatekey, @"otpauth://([^/]+)/([^?]+)\?(.*)", RegexOptions.IgnoreCase); if (match.Success) { authtype = match.Groups[1].Value; // @todo we only handle totp (not hotp) if (string.Compare(authtype, "totp", true) != 0) { WinAuthForm.ErrorDialog(Owner, "Only time-based (TOTP) authenticators are supported when adding a Google Authenticator. Use the general \"Add Authenticator\" for counter-based (HOTP) authenticators."); return(false); } var label = match.Groups[2].Value; if (string.IsNullOrEmpty(label) == false) { Authenticator.Name = nameField.Text = label; } var qs = WinAuthHelper.ParseQueryString(match.Groups[3].Value); privatekey = qs["secret"] ?? privatekey; } // just get the hex chars privatekey = Regex.Replace(privatekey, @"[^0-9a-z]", "", RegexOptions.IgnoreCase); if (privatekey.Length == 0) { WinAuthForm.ErrorDialog(Owner, "The secret code is not valid"); return(false); } try { var auth = new GoogleAuthenticator(); auth.Enroll(privatekey); Authenticator.AuthenticatorData = auth; codeProgress.Visible = true; var key = Base32.getInstance().Encode(Authenticator.AuthenticatorData.SecretKey); secretCodeField.Text = Regex.Replace(key, ".{3}", "$0 ").Trim(); codeField.Text = auth.CurrentCode; if (auth.ServerTimeDiff == 0L && SyncErrorWarned == false) { SyncErrorWarned = true; MessageBox.Show(this, string.Format(strings.AuthenticatorSyncError, "Google"), WinAuthMain.APPLICATION_TITLE, MessageBoxButtons.OK, MessageBoxIcon.Warning); } } catch (Exception irre) { WinAuthForm.ErrorDialog(Owner, "Unable to create the authenticator. The secret code is probably invalid.", irre); return(false); } return(true); }
/// <summary> /// Enroll the authenticator with the server. public void Enroll(string b32key) { SecretKey = Base32.getInstance().Decode(b32key); Sync(); }
/// <summary> /// Set the private key for the authenticator /// </summary> /// <param name="b32key">base32 encoded key</param> public void Enroll(string b32key, long counter = 0) { SecretKey = Base32.getInstance().Decode(b32key); Counter = counter; }
/// <summary> /// Verify and create the authenticator if needed /// </summary> /// <returns>true is successful</returns> private bool verifyAuthenticator(string privatekey) { if (string.IsNullOrEmpty(privatekey) == true) { return(false); } this.Authenticator.Name = nameField.Text; int digits = (this.Authenticator.AuthenticatorData != null ? this.Authenticator.AuthenticatorData.CodeDigits : GoogleAuthenticator.DEFAULT_CODE_DIGITS); string authtype = timeBasedRadio.Checked == true ? TOTP : HOTP; long counter = 0; // if this is a URL, pull it down Uri uri; Match match; if (Regex.IsMatch(privatekey, "https?://.*") == true && Uri.TryCreate(privatekey, UriKind.Absolute, out uri) == true) { try { var request = (HttpWebRequest)WebRequest.Create(uri); request.AllowAutoRedirect = true; request.Timeout = 20000; request.UserAgent = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)"; using (var response = (HttpWebResponse)request.GetResponse()) { if (response.StatusCode == HttpStatusCode.OK && response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) == true) { using (Bitmap bitmap = (Bitmap)Bitmap.FromStream(response.GetResponseStream())) { IBarcodeReader reader = new BarcodeReader(); var result = reader.Decode(bitmap); if (result != null) { privatekey = HttpUtility.UrlDecode(result.Text); } } } } } catch (Exception ex) { WinAuthForm.ErrorDialog(this.Owner, "Cannot load QR code image from " + privatekey, ex); return(false); } } else if ((match = Regex.Match(privatekey, @"data:image/([^;]+);base64,(.*)", RegexOptions.IgnoreCase)).Success == true) { byte[] imagedata = Convert.FromBase64String(match.Groups[2].Value); using (MemoryStream ms = new MemoryStream(imagedata)) { using (Bitmap bitmap = (Bitmap)Bitmap.FromStream(ms)) { IBarcodeReader reader = new BarcodeReader(); var result = reader.Decode(bitmap); if (result != null) { privatekey = HttpUtility.UrlDecode(result.Text); } } } } else if (IsValidFile(privatekey) == true) { // assume this is the image file using (Bitmap bitmap = (Bitmap)Bitmap.FromFile(privatekey)) { IBarcodeReader reader = new BarcodeReader(); var result = reader.Decode(bitmap); if (result != null) { privatekey = result.Text; } } } // check for otpauth://, e.g. "otpauth://totp/[email protected]?secret=IHZJDKAEEC774BMUK3GX6SA" match = Regex.Match(privatekey, @"otpauth://([^/]+)/([^?]+)\?(.*)", RegexOptions.IgnoreCase); if (match.Success == true) { authtype = match.Groups[1].Value.ToLower(); string label = match.Groups[2].Value; NameValueCollection qs = WinAuthHelper.ParseQueryString(match.Groups[3].Value); privatekey = qs["secret"] ?? privatekey; int querydigits; if (int.TryParse(qs["digits"], out querydigits) && querydigits != 0) { digits = querydigits; } if (qs["counter"] != null) { long.TryParse(qs["counter"], out counter); } string issuer = qs["issuer"]; if (string.IsNullOrEmpty(issuer) == false) { label = issuer + (string.IsNullOrEmpty(label) == false ? " (" + label + ")" : string.Empty); } if (string.IsNullOrEmpty(label) == false) { this.Authenticator.Name = this.nameField.Text = label; } } // just get the hex chars privatekey = Regex.Replace(privatekey, @"[^0-9a-z]", "", RegexOptions.IgnoreCase); if (privatekey.Length == 0) { WinAuthForm.ErrorDialog(this.Owner, "The secret code is not valid"); return(false); } try { Authenticator auth; if (authtype == TOTP) { auth = new GoogleAuthenticator(); ((GoogleAuthenticator)auth).Enroll(privatekey); timer.Enabled = true; codeProgress.Visible = true; timeBasedRadio.Checked = true; } else if (authtype == HOTP) { auth = new HOTPAuthenticator(); if (counterField.Text.Trim().Length != 0) { long.TryParse(counterField.Text.Trim(), out counter); } ((HOTPAuthenticator)auth).Enroll(privatekey, counter); // start with the next code timer.Enabled = false; codeProgress.Visible = false; counterBasedRadio.Checked = true; } else { WinAuthForm.ErrorDialog(this.Owner, "Only TOTP or HOTP authenticators are supported"); return(false); } auth.CodeDigits = digits; this.Authenticator.AuthenticatorData = auth; codeField.SpaceOut = digits / 2; string key = Base32.getInstance().Encode(this.Authenticator.AuthenticatorData.SecretKey); //this.secretCodeField.Text = Regex.Replace(key, ".{3}", "$0 ").Trim(); this.codeField.Text = auth.CurrentCode; if (!(auth is HOTPAuthenticator) && auth.ServerTimeDiff == 0L && SyncErrorWarned == false) { SyncErrorWarned = true; MessageBox.Show(this, string.Format(strings.AuthenticatorSyncError, "Google"), WinAuthMain.APPLICATION_TITLE, MessageBoxButtons.OK, MessageBoxIcon.Warning); } } catch (Exception irre) { WinAuthForm.ErrorDialog(this.Owner, "Unable to create the authenticator. The secret code is probably invalid.", irre); return(false); } return(true); }