package de.pinyto.ctSESAM;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;
import android.util.Log;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Set;
/**
* Use this class to manage password settings. It will store them internally and it will also
* pack them for synchronization.
*/
public class PasswordSettingsManager {
private SharedPreferences savedDomains;
private Context contentContext;
private Set<PasswordSetting> settings;
PasswordSettingsManager(Context contentContext) {
this.contentContext = contentContext;
this.savedDomains = contentContext.getSharedPreferences(
"savedDomains", Context.MODE_PRIVATE);
this.settings = new HashSet<>();
}
private Crypter getSettingsCrypter(KgkManager kgkManager) {
byte[] salt2 = kgkManager.getSalt2();
byte[] iv2 = kgkManager.getIv2();
byte[] kgk = kgkManager.getKgk();
byte[] settingsKey = Crypter.createKey(kgk, salt2);
byte[] settingsKeyIv = new byte[48];
for (int i = 0; i < settingsKey.length; i++) {
settingsKeyIv[i] = settingsKey[i];
settingsKey[i] = 0x00;
}
System.arraycopy(iv2, 0, settingsKeyIv, settingsKey.length, iv2.length);
return new Crypter(settingsKeyIv);
}
public void loadLocalSettings(KgkManager kgkManager) throws WrongPasswordException {
Crypter settingsCrypter = this.getSettingsCrypter(kgkManager);
byte[] encrypted = Base64.decode(
this.savedDomains.getString("encryptedSettings", ""),
Base64.DEFAULT);
if (encrypted.length < 40) {
return;
}
byte[] decrypted = settingsCrypter.decrypt(encrypted);
if (decrypted.length < 40) {
throw new WrongPasswordException("wrong length: too short");
}
String decompressedSettings = Packer.decompress(decrypted);
if (decompressedSettings.length() <= 0) {
throw new WrongPasswordException("unable to decompress");
}
try {
JSONObject decryptedObject = new JSONObject(decompressedSettings);
JSONObject decryptedSettings = decryptedObject.getJSONObject("settings");
JSONArray syncedSettings = decryptedObject.getJSONArray("synced");
Iterator<String> keys = decryptedSettings.keys();
while (keys.hasNext()) {
String key = keys.next();
JSONObject settingObject = decryptedSettings.getJSONObject(key);
boolean found = false;
for (PasswordSetting setting : this.settings) {
if (setting.getDomain().contentEquals(key)) {
found = true;
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
Locale.ENGLISH);
Date modifiedRemote = df.parse(settingObject.getString("mDate"));
if (modifiedRemote.after(setting.getMDate())) {
setting.loadFromJSON(settingObject);
boolean foundInSynced = false;
for (int i = 0; i < syncedSettings.length(); i++) {
if (syncedSettings.getString(i).contentEquals(key)) {
foundInSynced = true;
}
}
setting.setSynced(foundInSynced);
}
}
}
if (!found) {
PasswordSetting newSetting = new PasswordSetting(key);
newSetting.loadFromJSON(settingObject);
boolean foundInSynced = false;
for (int i = 0; i < syncedSettings.length(); i++) {
if (syncedSettings.getString(i).contentEquals(key)) {
foundInSynced = true;
}
}
newSetting.setSynced(foundInSynced);
this.settings.add(newSetting);
}
}
} catch (JSONException jsonError) {
Log.d("Settings loading error", "The loaded settings are not in JSON format.");
jsonError.printStackTrace();
} catch (ParseException timeFormatError) {
Log.d("Settings loading error",
"The loaded settings contain time information in a wrong format.");
timeFormatError.printStackTrace();
}
}
public void storeLocalSettings(KgkManager kgkManager) {
kgkManager.freshSalt2();
kgkManager.freshIv2();
Crypter settingsCrypter = this.getSettingsCrypter(kgkManager);
JSONObject storeStructure = new JSONObject();
try {
storeStructure.put("settings", this.getSettingsAsJSON());
storeStructure.put("synced", this.getSyncedSettings());
} catch (JSONException jsonError) {
Log.d("Settings saving error", "Could not construct JSON structure for storage.");
jsonError.printStackTrace();
}
SharedPreferences.Editor savedDomainsEditor = savedDomains.edit();
if (settingsCrypter != null) {
byte[] encryptedSettings = settingsCrypter.encrypt(
Packer.compress(storeStructure.toString()));
savedDomainsEditor.putString("encryptedSettings",
Base64.encodeToString(
encryptedSettings,
Base64.DEFAULT));
savedDomainsEditor.apply();
kgkManager.storeLocalKgkBlock();
}
}
public PasswordSetting getSetting(String domain) {
for (PasswordSetting setting : this.settings) {
if (setting.getDomain().contentEquals(domain)) {
return setting;
}
}
PasswordSetting newSetting = new PasswordSetting(domain);
this.settings.add(newSetting);
return newSetting;
}
public void setSetting(PasswordSetting changed) {
for (PasswordSetting setting : this.settings) {
if (setting.getDomain().contentEquals(changed.getDomain())) {
this.settings.remove(setting);
break;
}
}
this.settings.add(changed);
}
public void deleteSetting(String domain) {
for (PasswordSetting setting : this.settings) {
if (setting.getDomain().contentEquals(domain)) {
this.settings.remove(setting);
}
}
}
public String[] getDomainList() {
String[] domainList = new String[this.settings.size()];
int i = 0;
for (PasswordSetting setting : this.settings) {
domainList[i] = setting.getDomain();
i++;
}
return domainList;
}
private JSONObject getSettingsAsJSON() {
JSONObject settings = new JSONObject();
try {
for (PasswordSetting setting : this.settings) {
settings.put(setting.getDomain(), setting.toJSON());
}
} catch (JSONException jsonError) {
Log.d("Settings packing error", "Could not create json.");
jsonError.printStackTrace();
}
return settings;
}
private JSONArray getSyncedSettings() {
JSONArray syncedSettings = new JSONArray();
for (PasswordSetting setting : this.settings) {
if (setting.isSynced()) {
syncedSettings.put(setting.getDomain());
}
}
return syncedSettings;
}
public byte[] getExportData(KgkManager kgkManager) {
kgkManager.freshIv2();
kgkManager.freshSalt2();
byte[] kgkBlock = kgkManager.getEncryptedKgk();
Crypter settingsCrypter = this.getSettingsCrypter(kgkManager);
byte[] encryptedSettings = settingsCrypter.encrypt(
Packer.compress(
this.getSettingsAsJSON().toString()
)
);
byte[] salt = kgkManager.getKgkCrypterSalt();
byte[] exportData = new byte[1 + salt.length + kgkBlock.length + encryptedSettings.length];
exportData[0] = 0x01;
System.arraycopy(salt, 0, exportData, 1, salt.length);
System.arraycopy(kgkBlock, 0, exportData, 1 + salt.length, kgkBlock.length);
System.arraycopy(encryptedSettings, 0,
exportData, 1 + salt.length + kgkBlock.length, encryptedSettings.length);
return exportData;
}
public boolean updateFromExportData(KgkManager kgkManager, byte[] blob) {
if (!(blob[0] == 0x01)) {
Log.d("Version error", "Wrong data format. Could not import anything.");
return true;
}
byte[] encryptedSettings = Arrays.copyOfRange(blob, 145, blob.length);
Crypter settingsCrypter = this.getSettingsCrypter(kgkManager);
byte[] decryptedSettings = settingsCrypter.decrypt(encryptedSettings);
if (decryptedSettings.length <= 0) {
Toast.makeText(contentContext, R.string.sync_wrong_password,
Toast.LENGTH_SHORT).show();
return false;
}
String jsonString = Packer.decompress(decryptedSettings);
try {
JSONObject loadedSettings = new JSONObject(jsonString);
boolean updateRemote = false;
Iterator<String> loadedSettingsIterator = loadedSettings.keys();
while (loadedSettingsIterator.hasNext()) {
JSONObject loadedSetting = loadedSettings.getJSONObject(
loadedSettingsIterator.next());
boolean found = false;
for (String domain : this.getDomainList()) {
PasswordSetting setting = this.getSetting(domain);
if (setting.getDomain().equals(loadedSetting.getString("domain"))) {
found = true;
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
Locale.ENGLISH);
Date modifiedRemote = df.parse(loadedSetting.getString("mDate"));
if (modifiedRemote.after(setting.getMDate())) {
setting.loadFromJSON(loadedSetting);
this.setSetting(setting);
} else {
updateRemote = true;
}
setting.setSynced(true);
break;
}
}
if (!found) {
PasswordSetting newSetting = new PasswordSetting(
loadedSetting.getString("domain"));
newSetting.loadFromJSON(loadedSetting);
newSetting.setSynced(true);
this.setSetting(newSetting);
}
}
for (String domain : this.getDomainList()) {
PasswordSetting setting = this.getSetting(domain);
boolean found = false;
Iterator<String> loadedSettingsIterator2 = loadedSettings.keys();
while (loadedSettingsIterator2.hasNext()) {
JSONObject loadedSetting = loadedSettings.getJSONObject(
loadedSettingsIterator2.next());
if (setting.getDomain().equals(loadedSetting.getString("domain"))) {
found = true;
break;
}
}
if (!found && setting.isSynced()) {
updateRemote = true;
}
}
this.storeLocalSettings(kgkManager);
return updateRemote;
} catch (JSONException e) {
Log.d("Update settings error", "Unable to read JSON data.");
e.printStackTrace();
return false;
} catch (ParseException e) {
Log.d("Update settings error", "Unable to parse the date.");
e.printStackTrace();
return false;
}
}
public void setAllSettingsToSynced() {
for (PasswordSetting setting : this.settings) {
setting.setSynced(true);
}
}
}