/// <summary> /// Load an authenticator from a Stream with an explicit password for this current version /// </summary> /// <param name="stream">Stream to read</param> /// <param name="password">explicit password if requried</param> /// <param name="version">expected version of authenticator</param> /// <returns>loaded Authenticator</returns> public static Authenticator ReadFromStream(Stream stream, string password, decimal version) { using (XmlReader xr = XmlReader.Create(stream)) { XmlDocument doc = new XmlDocument(); doc.Load(xr); XmlNode rootnode = doc.DocumentElement; XmlNode node; Authenticator authenticator = null; // get the type is we have it, else use the default XmlAttribute authenticatorType = rootnode.Attributes["type"]; if (authenticatorType != null) { Type type = System.Reflection.Assembly.GetExecutingAssembly().GetType(authenticatorType.Value, false, true); authenticator = Activator.CreateInstance(type) as Authenticator; } if (authenticator == null) { authenticator = new BattleNetAuthenticator(); } // is the Mobile Authenticator file? <xml.../><map>...</map> node = rootnode.SelectSingleNode("/map/string[@name='" + BMA_HASH_NAME + "']"); if (node != null) { string data = node.InnerText; // extract the secret key and serial byte[] bytes = StringToByteArray(data); // decrpyt with the fixed key for (int i = bytes.Length - 1; i >= 0; i--) { bytes[i] ^= MOBILE_AUTHENTICATOR_KEY[i]; } // decode and set members string full = Encoding.UTF8.GetString(bytes, 0, bytes.Length); authenticator.SecretData = full; // get offset value long offset = 0; node = rootnode.SelectSingleNode("/map/long[@name='" + BMA_OFFSET_NAME + "']"); if (node != null && LongTryParse(node.Attributes["value"].InnerText, out offset) /* long.TryParse(node.Attributes["value"].InnerText, out offset) == true */) { authenticator.ServerTimeDiff = offset; } return(authenticator); } // read <= 1.6 config if (version <= (decimal)1.6 && (node = rootnode.SelectSingleNode("secretdata")) != null) { // save off the processed decryptions so we can send bug report List <string> datas = new List <string>(); string data = node.InnerText; datas.Add(data); XmlAttribute attr = node.Attributes["encrypted"]; if (attr != null && attr.InnerText.Length != 0) { string encryptedType = attr.InnerText; if (encryptedType == "u") { // we are going to decrypt with the Windows User account key authenticator.PasswordType = PasswordTypes.User; byte[] cipher = StringToByteArray(data); byte[] plain = ProtectedData.Unprotect(cipher, null, DataProtectionScope.CurrentUser); data = ByteArrayToString(plain); datas.Add(data); } else if (encryptedType == "m") { // we are going to decrypt with the Windows local machine key authenticator.PasswordType = PasswordTypes.Machine; byte[] cipher = StringToByteArray(data); byte[] plain = ProtectedData.Unprotect(cipher, null, DataProtectionScope.LocalMachine); data = ByteArrayToString(plain); datas.Add(data); } else if (encryptedType == "y") { // we use an explicit password to encrypt data if (string.IsNullOrEmpty(password) == true) { throw new EncrpytedSecretDataException(); } authenticator.PasswordType = PasswordTypes.Explicit; authenticator.Password = password; data = Decrypt(data, password, false); datas.Add(data); } } try { authenticator.SecretData = ConvertAndriodSecretData(data); } catch (Exception ex) { // we get a decode error if the data decrypted but isn't valid throw new InvalidSecretDataException(ex, password, (attr != null ? attr.InnerText : null), datas); } long offset = 0; node = rootnode.SelectSingleNode("servertimediff"); if (node != null && LongTryParse(node.InnerText, out offset) == true /* long.TryParse(node.InnerText, out offset) == true */) { authenticator.ServerTimeDiff = offset; } node = rootnode.SelectSingleNode("restorecodeverified"); if (node != null && string.Compare(node.InnerText, bool.TrueString.ToLower(), true) == 0) { authenticator.RestoreCodeVerified = true; } return(authenticator); } // read current config if ((node = rootnode.SelectSingleNode("secretdata")) != null) { // save off the processed decryptions so we can send bug report string data = node.InnerText; XmlAttribute attr = node.Attributes["encrypted"]; List <string> datas = new List <string>(); datas.Add(data); PasswordTypes passwordType = PasswordTypes.None; if (attr != null && attr.InnerText.Length != 0) { char[] encTypes = attr.InnerText.ToCharArray(); // we read the string in reverse order (the order they were encrypted) for (int i = encTypes.Length - 1; i >= 0; i--) { char encryptedType = encTypes[i]; switch (encryptedType) { case 'u': { // we are going to decrypt with the Windows User account key try { passwordType |= PasswordTypes.User; byte[] cipher = StringToByteArray(data); byte[] plain = ProtectedData.Unprotect(cipher, null, DataProtectionScope.CurrentUser); data = ByteArrayToString(plain); datas.Add(data); } catch (System.Security.Cryptography.CryptographicException) { throw new InvalidUserDecryptionException(); } break; } case 'm': { // we are going to decrypt with the Windows local machine key try { passwordType |= PasswordTypes.Machine; byte[] cipher = StringToByteArray(data); byte[] plain = ProtectedData.Unprotect(cipher, null, DataProtectionScope.LocalMachine); data = ByteArrayToString(plain); datas.Add(data); } catch (System.Security.Cryptography.CryptographicException) { throw new InvalidMachineDecryptionException(); } break; } case 'y': { // we use an explicit password to encrypt data if (string.IsNullOrEmpty(password) == true) { throw new EncrpytedSecretDataException(); } passwordType |= PasswordTypes.Explicit; authenticator.Password = password; data = Decrypt(data, password, true); datas.Add(data); break; } default: break; } } authenticator.PasswordType = passwordType; } try { // pre-version 2 we kept compatability with the Android file if (version < (decimal)2) { data = ConvertAndriodSecretData(data); } authenticator.SecretData = data; } catch (Exception ex) { throw new InvalidSecretDataException(ex, password, (attr != null ? attr.InnerText : null), datas); } long offset = 0; node = rootnode.SelectSingleNode("servertimediff"); if (node != null && LongTryParse(node.InnerText, out offset) == true /* long.TryParse(node.InnerText, out offset) == true */) { authenticator.ServerTimeDiff = offset; } node = rootnode.SelectSingleNode("restorecodeverified"); if (node != null && string.Compare(node.InnerText, bool.TrueString.ToLower(), true) == 0) { authenticator.RestoreCodeVerified = true; } return(authenticator); } throw new InvalidOperationException(); } }
/// <summary> /// Load an authenticator from a Stream with an explicit password for this current version /// </summary> /// <param name="stream">Stream to read</param> /// <param name="password">explicit password if requried</param> /// <param name="version">expected version of authenticator</param> /// <returns>loaded Authenticator</returns> public static Authenticator ReadFromStream(Stream stream, string password, decimal version) { using (XmlReader xr = XmlReader.Create(stream)) { XmlDocument doc = new XmlDocument(); doc.Load(xr); XmlNode rootnode = doc.DocumentElement; XmlNode node; Authenticator authenticator = null; // get the type is we have it, else use the default XmlAttribute authenticatorType = rootnode.Attributes["type"]; if (authenticatorType != null) { Type type = System.Reflection.Assembly.GetExecutingAssembly().GetType(authenticatorType.Value, false, true); authenticator = Activator.CreateInstance(type) as Authenticator; } if (authenticator == null) { authenticator = new BattleNetAuthenticator(); } // is the Mobile Authenticator file? <xml.../><map>...</map> node = rootnode.SelectSingleNode("/map/string[@name='" + BMA_HASH_NAME + "']"); if (node != null) { string data = node.InnerText; // extract the secret key and serial byte[] bytes = StringToByteArray(data); // decrpyt with the fixed key for (int i = bytes.Length - 1; i >= 0; i--) { bytes[i] ^= MOBILE_AUTHENTICATOR_KEY[i]; } // decode and set members string full = Encoding.UTF8.GetString(bytes, 0, bytes.Length); authenticator.SecretData = full; // get offset value long offset = 0; node = rootnode.SelectSingleNode("/map/long[@name='" + BMA_OFFSET_NAME + "']"); if (node != null && LongTryParse(node.Attributes["value"].InnerText, out offset) /* long.TryParse(node.Attributes["value"].InnerText, out offset) == true */) { authenticator.ServerTimeDiff = offset; } return authenticator; } // read <= 1.6 config if (version <= (decimal)1.6 && (node = rootnode.SelectSingleNode("secretdata")) != null) { // save off the processed decryptions so we can send bug report List<string> datas = new List<string>(); string data = node.InnerText; datas.Add(data); XmlAttribute attr = node.Attributes["encrypted"]; if (attr != null && attr.InnerText.Length != 0) { string encryptedType = attr.InnerText; if (encryptedType == "u") { // we are going to decrypt with the Windows User account key authenticator.PasswordType = PasswordTypes.User; byte[] cipher = StringToByteArray(data); byte[] plain = ProtectedData.Unprotect(cipher, null, DataProtectionScope.CurrentUser); data = ByteArrayToString(plain); datas.Add(data); } else if (encryptedType == "m") { // we are going to decrypt with the Windows local machine key authenticator.PasswordType = PasswordTypes.Machine; byte[] cipher = StringToByteArray(data); byte[] plain = ProtectedData.Unprotect(cipher, null, DataProtectionScope.LocalMachine); data = ByteArrayToString(plain); datas.Add(data); } else if (encryptedType == "y") { // we use an explicit password to encrypt data if (string.IsNullOrEmpty(password) == true) { throw new EncrpytedSecretDataException(); } authenticator.PasswordType = PasswordTypes.Explicit; authenticator.Password = password; data = Decrypt(data, password, false); datas.Add(data); } } try { authenticator.SecretData = ConvertAndriodSecretData(data); } catch (Exception ex) { // we get a decode error if the data decrypted but isn't valid throw new InvalidSecretDataException(ex, password, (attr != null ? attr.InnerText : null), datas); } long offset = 0; node = rootnode.SelectSingleNode("servertimediff"); if (node != null && LongTryParse(node.InnerText, out offset) == true /* long.TryParse(node.InnerText, out offset) == true */) { authenticator.ServerTimeDiff = offset; } node = rootnode.SelectSingleNode("restorecodeverified"); if (node != null && string.Compare(node.InnerText, bool.TrueString.ToLower(), true) == 0) { authenticator.RestoreCodeVerified = true; } return authenticator; } // read current config if ((node = rootnode.SelectSingleNode("secretdata")) != null) { // save off the processed decryptions so we can send bug report string data = node.InnerText; XmlAttribute attr = node.Attributes["encrypted"]; List<string> datas = new List<string>(); datas.Add(data); PasswordTypes passwordType = PasswordTypes.None; if (attr != null && attr.InnerText.Length != 0) { char[] encTypes = attr.InnerText.ToCharArray(); // we read the string in reverse order (the order they were encrypted) for (int i = encTypes.Length - 1; i >= 0; i--) { char encryptedType = encTypes[i]; switch (encryptedType) { case 'u': { // we are going to decrypt with the Windows User account key try { passwordType |= PasswordTypes.User; byte[] cipher = StringToByteArray(data); byte[] plain = ProtectedData.Unprotect(cipher, null, DataProtectionScope.CurrentUser); data = ByteArrayToString(plain); datas.Add(data); } catch (System.Security.Cryptography.CryptographicException) { throw new InvalidUserDecryptionException(); } break; } case 'm': { // we are going to decrypt with the Windows local machine key try { passwordType |= PasswordTypes.Machine; byte[] cipher = StringToByteArray(data); byte[] plain = ProtectedData.Unprotect(cipher, null, DataProtectionScope.LocalMachine); data = ByteArrayToString(plain); datas.Add(data); } catch (System.Security.Cryptography.CryptographicException) { throw new InvalidMachineDecryptionException(); } break; } case 'y': { // we use an explicit password to encrypt data if (string.IsNullOrEmpty(password) == true) { throw new EncrpytedSecretDataException(); } passwordType |= PasswordTypes.Explicit; authenticator.Password = password; data = Decrypt(data, password, true); datas.Add(data); break; } default: break; } } authenticator.PasswordType = passwordType; } try { // pre-version 2 we kept compatability with the Android file if (version < (decimal)2) { data = ConvertAndriodSecretData(data); } authenticator.SecretData = data; } catch (Exception ex) { throw new InvalidSecretDataException(ex, password, (attr != null ? attr.InnerText : null), datas); } long offset = 0; node = rootnode.SelectSingleNode("servertimediff"); if (node != null && LongTryParse(node.InnerText, out offset) == true /* long.TryParse(node.InnerText, out offset) == true */) { authenticator.ServerTimeDiff = offset; } node = rootnode.SelectSingleNode("restorecodeverified"); if (node != null && string.Compare(node.InnerText, bool.TrueString.ToLower(), true) == 0) { authenticator.RestoreCodeVerified = true; } return authenticator; } throw new InvalidOperationException(); } }