Use the new CryptoManager instead of the main encryption password.

This will give us more flexibility in the future because now the
encryption key and derivation is all managed in one place.
pull/5/head
Tom Hacohen 7 years ago
parent 4004eca762
commit d2eaf5f434

@ -73,19 +73,15 @@ abstract class BaseManager {
return uid; return uid;
} }
public String getContent(String keyBase64) { public String getContent(Crypto.CryptoManager crypto) {
// FIXME: probably cache encryption object return new String(crypto.decrypt(content), Charsets.UTF_8);
Crypto.CryptoManager cryptoManager = new Crypto.CryptoManager(keyBase64, null);
return new String(cryptoManager.decrypt(content), Charsets.UTF_8);
} }
void setContent(String keyBase64, String content) { void setContent(Crypto.CryptoManager crypto, String content) {
// FIXME: probably cache encryption object this.content = crypto.encrypt(content.getBytes(Charsets.UTF_8));
Crypto.CryptoManager cryptoManager = new Crypto.CryptoManager(keyBase64, null);
this.content = cryptoManager.encrypt(content.getBytes(Charsets.UTF_8));
} }
byte[] calculateHmac(String keyBase64, String uuid) { byte[] calculateHmac(Crypto.CryptoManager crypto, String uuid) {
ByteArrayOutputStream hashContent = new ByteArrayOutputStream(); ByteArrayOutputStream hashContent = new ByteArrayOutputStream();
try { try {
@ -99,16 +95,14 @@ abstract class BaseManager {
return "DEADBEEFDEADBEEFDEADBEEFDEADBEEF".getBytes(); return "DEADBEEFDEADBEEFDEADBEEFDEADBEEF".getBytes();
} }
// FIXME: probably cache encryption object return crypto.hmac(hashContent.toByteArray());
Crypto.CryptoManager cryptoManager = new Crypto.CryptoManager(keyBase64, null);
return cryptoManager.hmac(hashContent.toByteArray());
} }
protected Base() { protected Base() {
} }
Base(String keyBase64, String content, String uid) { Base(Crypto.CryptoManager crypto, String content, String uid) {
setContent(keyBase64, content); setContent(crypto, content);
setUid(uid); setUid(uid);
} }

@ -31,12 +31,12 @@ public class Crypto {
return Base64.encodeToString(SCrypt.generate(password.getBytes(Charsets.UTF_8), salt.getBytes(Charsets.UTF_8), 16384, 8, 1, keySize), Base64.NO_WRAP); return Base64.encodeToString(SCrypt.generate(password.getBytes(Charsets.UTF_8), salt.getBytes(Charsets.UTF_8), 16384, 8, 1, keySize), Base64.NO_WRAP);
} }
static class CryptoManager { public static class CryptoManager {
private SecureRandom _random = null; private SecureRandom _random = null;
private final byte[] cipherKey; private final byte[] cipherKey;
private final byte[] hmacKey; private final byte[] hmacKey;
CryptoManager(String keyBase64, String salt) { public CryptoManager(String keyBase64, String salt) {
byte[] derivedKey; // FIXME use salt = hmac256(salt.getBytes(Charsets.UTF_8), Base64.decode(keyBase64, Base64.NO_WRAP)); byte[] derivedKey; // FIXME use salt = hmac256(salt.getBytes(Charsets.UTF_8), Base64.decode(keyBase64, Base64.NO_WRAP));
derivedKey = Base64.decode(keyBase64, Base64.NO_WRAP); derivedKey = Base64.decode(keyBase64, Base64.NO_WRAP);
cipherKey = hmac256("aes".getBytes(Charsets.UTF_8), derivedKey); cipherKey = hmac256("aes".getBytes(Charsets.UTF_8), derivedKey);

@ -35,7 +35,7 @@ public class JournalEntryManager extends BaseManager {
this.client = httpClient; this.client = httpClient;
} }
public List<Entry> getEntries(String keyBase64, String last) throws Exceptions.HttpException, Exceptions.IntegrityException { public List<Entry> getEntries(Crypto.CryptoManager crypto, String last) throws Exceptions.HttpException, Exceptions.IntegrityException {
Entry previousEntry = null; Entry previousEntry = null;
HttpUrl.Builder urlBuilder = this.remote.newBuilder(); HttpUrl.Builder urlBuilder = this.remote.newBuilder();
if (last != null) { if (last != null) {
@ -55,7 +55,7 @@ public class JournalEntryManager extends BaseManager {
List<Entry> ret = GsonHelper.gson.fromJson(body.charStream(), entryType); List<Entry> ret = GsonHelper.gson.fromJson(body.charStream(), entryType);
for (Entry entry : ret) { for (Entry entry : ret) {
entry.verify(keyBase64, previousEntry); entry.verify(crypto, previousEntry);
previousEntry = entry; previousEntry = entry;
} }
@ -85,13 +85,13 @@ public class JournalEntryManager extends BaseManager {
super(); super();
} }
public void update(String keyBase64, String content, Entry previous) { public void update(Crypto.CryptoManager crypto, String content, Entry previous) {
setContent(keyBase64, content); setContent(crypto, content);
setUid(calculateHmac(keyBase64, previous)); setUid(calculateHmac(crypto, previous));
} }
void verify(String keyBase64, Entry previous) throws Exceptions.IntegrityException { void verify(Crypto.CryptoManager crypto, Entry previous) throws Exceptions.IntegrityException {
String correctHash = calculateHmac(keyBase64, previous); String correctHash = calculateHmac(crypto, previous);
if (!getUuid().equals(correctHash)) { if (!getUuid().equals(correctHash)) {
throw new Exceptions.IntegrityException("Bad HMAC. " + getUuid() + " != " + correctHash); throw new Exceptions.IntegrityException("Bad HMAC. " + getUuid() + " != " + correctHash);
} }
@ -103,13 +103,13 @@ public class JournalEntryManager extends BaseManager {
return ret; return ret;
} }
private String calculateHmac(String keyBase64, Entry previous) { private String calculateHmac(Crypto.CryptoManager crypto, Entry previous) {
String uuid = null; String uuid = null;
if (previous != null) { if (previous != null) {
uuid = previous.getUuid(); uuid = previous.getUuid();
} }
return Crypto.toHex(calculateHmac(keyBase64, uuid)); return Crypto.toHex(calculateHmac(crypto, uuid));
} }
} }

@ -46,8 +46,9 @@ public class JournalManager extends BaseManager {
List<Journal> ret = GsonHelper.gson.fromJson(body.charStream(), journalType); List<Journal> ret = GsonHelper.gson.fromJson(body.charStream(), journalType);
for (Journal journal : ret) { for (Journal journal : ret) {
Crypto.CryptoManager crypto = new Crypto.CryptoManager(keyBase64, journal.getUuid());
journal.processFromJson(); journal.processFromJson();
journal.verify(keyBase64); journal.verify(crypto);
} }
return ret; return ret;
@ -95,13 +96,9 @@ public class JournalManager extends BaseManager {
super(); super();
} }
public Journal(String keyBase64, String content) { public Journal(Crypto.CryptoManager crypto, String content, String uid) {
this(keyBase64, content, sha256(UUID.randomUUID().toString())); super(crypto, content, uid);
} hmac = calculateHmac(crypto);
public Journal(String keyBase64, String content, String uid) {
super(keyBase64, content, uid);
hmac = calculateHmac(keyBase64);
} }
private void processFromJson() { private void processFromJson() {
@ -109,19 +106,23 @@ public class JournalManager extends BaseManager {
setContent(Arrays.copyOfRange(getContent(), hmacSize, getContent().length)); setContent(Arrays.copyOfRange(getContent(), hmacSize, getContent().length));
} }
void verify(String keyBase64) throws Exceptions.IntegrityException { void verify(Crypto.CryptoManager crypto) throws Exceptions.IntegrityException {
if (hmac == null) { if (hmac == null) {
throw new Exceptions.IntegrityException("HMAC is null!"); throw new Exceptions.IntegrityException("HMAC is null!");
} }
byte[] correctHash = calculateHmac(keyBase64); byte[] correctHash = calculateHmac(crypto);
if (!Arrays.areEqual(hmac, correctHash)) { if (!Arrays.areEqual(hmac, correctHash)) {
throw new Exceptions.IntegrityException("Bad HMAC. " + toHex(hmac) + " != " + toHex(correctHash)); throw new Exceptions.IntegrityException("Bad HMAC. " + toHex(hmac) + " != " + toHex(correctHash));
} }
} }
byte[] calculateHmac(String keyBase64) { byte[] calculateHmac(Crypto.CryptoManager crypto) {
return super.calculateHmac(keyBase64, getUuid()); return super.calculateHmac(crypto, getUuid());
}
public static String genUid() {
return sha256(UUID.randomUUID().toString());
} }
@Override @Override

@ -1,6 +1,7 @@
package com.etesync.syncadapter.model; package com.etesync.syncadapter.model;
import com.etesync.syncadapter.GsonHelper; import com.etesync.syncadapter.GsonHelper;
import com.etesync.syncadapter.journalmanager.Crypto;
import com.etesync.syncadapter.journalmanager.JournalEntryManager; import com.etesync.syncadapter.journalmanager.JournalEntryManager;
import lombok.Getter; import lombok.Getter;
@ -41,8 +42,8 @@ public class SyncEntry {
return this.action.equals(action); return this.action.equals(action);
} }
public static SyncEntry fromJournalEntry(String keyBase64, JournalEntryManager.Entry entry) { public static SyncEntry fromJournalEntry(Crypto.CryptoManager crypto, JournalEntryManager.Entry entry) {
return fromJson(entry.getContent(keyBase64)); return fromJson(entry.getContent(crypto));
} }
static SyncEntry fromJson(String json) { static SyncEntry fromJson(String json) {

@ -45,6 +45,7 @@ import com.etesync.syncadapter.Constants;
import com.etesync.syncadapter.HttpClient; import com.etesync.syncadapter.HttpClient;
import com.etesync.syncadapter.InvalidAccountException; import com.etesync.syncadapter.InvalidAccountException;
import com.etesync.syncadapter.R; import com.etesync.syncadapter.R;
import com.etesync.syncadapter.journalmanager.Crypto;
import com.etesync.syncadapter.journalmanager.Exceptions; import com.etesync.syncadapter.journalmanager.Exceptions;
import com.etesync.syncadapter.journalmanager.JournalManager; import com.etesync.syncadapter.journalmanager.JournalManager;
import com.etesync.syncadapter.model.CollectionInfo; import com.etesync.syncadapter.model.CollectionInfo;
@ -160,7 +161,8 @@ public abstract class SyncAdapterService extends Service {
List<CollectionInfo> collections = new LinkedList<>(); List<CollectionInfo> collections = new LinkedList<>();
for (JournalManager.Journal journal : journalsManager.getJournals(settings.password())) { for (JournalManager.Journal journal : journalsManager.getJournals(settings.password())) {
CollectionInfo info = CollectionInfo.fromJson(journal.getContent(settings.password())); Crypto.CryptoManager crypto = new Crypto.CryptoManager(settings.password(), journal.getUuid());
CollectionInfo info = CollectionInfo.fromJson(journal.getContent(crypto));
info.url = journal.getUuid(); info.url = journal.getUuid();
if (info.type.equals(serviceType)) { if (info.type.equals(serviceType)) {
collections.add(info); collections.add(info);
@ -169,9 +171,10 @@ public abstract class SyncAdapterService extends Service {
if (collections.isEmpty()) { if (collections.isEmpty()) {
CollectionInfo info = CollectionInfo.defaultForServiceType(serviceType); CollectionInfo info = CollectionInfo.defaultForServiceType(serviceType);
JournalManager.Journal journal = new JournalManager.Journal(settings.password(), info.toJson()); info.url = JournalManager.Journal.genUid();
Crypto.CryptoManager crypto = new Crypto.CryptoManager(settings.password(), info.url);
JournalManager.Journal journal = new JournalManager.Journal(crypto, info.toJson(), info.url);
journalsManager.putJournal(journal); journalsManager.putJournal(journal);
info.url = journal.getUuid();
collections.add(info); collections.add(info);
} }

@ -21,6 +21,7 @@ import com.etesync.syncadapter.HttpClient;
import com.etesync.syncadapter.InvalidAccountException; import com.etesync.syncadapter.InvalidAccountException;
import com.etesync.syncadapter.NotificationHelper; import com.etesync.syncadapter.NotificationHelper;
import com.etesync.syncadapter.R; import com.etesync.syncadapter.R;
import com.etesync.syncadapter.journalmanager.Crypto;
import com.etesync.syncadapter.journalmanager.Exceptions; import com.etesync.syncadapter.journalmanager.Exceptions;
import com.etesync.syncadapter.journalmanager.JournalEntryManager; import com.etesync.syncadapter.journalmanager.JournalEntryManager;
import com.etesync.syncadapter.model.CollectionInfo; import com.etesync.syncadapter.model.CollectionInfo;
@ -69,6 +70,8 @@ abstract public class SyncManager {
protected JournalEntryManager journal; protected JournalEntryManager journal;
private JournalEntity _journalEntity; private JournalEntity _journalEntity;
private final Crypto.CryptoManager crypto;
private EntityDataStore<Persistable> data; private EntityDataStore<Persistable> data;
/** /**
@ -109,6 +112,8 @@ abstract public class SyncManager {
notificationManager = new NotificationHelper(context, journalUid, notificationId()); notificationManager = new NotificationHelper(context, journalUid, notificationId());
notificationManager.cancel(); notificationManager.cancel();
crypto = new Crypto.CryptoManager(settings.password(), journalUid);
data = ((App) context.getApplicationContext()).getData(); data = ((App) context.getApplicationContext()).getData();
} }
@ -249,7 +254,7 @@ abstract public class SyncManager {
i++; i++;
App.log.info("Processing (" + String.valueOf(i) + "/" + strTotal + ") " + entry.toString()); App.log.info("Processing (" + String.valueOf(i) + "/" + strTotal + ") " + entry.toString());
SyncEntry cEntry = SyncEntry.fromJournalEntry(settings.password(), entry); SyncEntry cEntry = SyncEntry.fromJournalEntry(crypto, entry);
persistSyncEntry(entry.getUuid(), cEntry); persistSyncEntry(entry.getUuid(), cEntry);
if (cEntry.isAction(SyncEntry.Actions.DELETE)) { if (cEntry.isAction(SyncEntry.Actions.DELETE)) {
continue; continue;
@ -266,10 +271,10 @@ abstract public class SyncManager {
int count = data.count(EntryEntity.class).where(EntryEntity.JOURNAL.eq(getJournalEntity())).get().value(); int count = data.count(EntryEntity.class).where(EntryEntity.JOURNAL.eq(getJournalEntity())).get().value();
if ((remoteCTag != null) && (count == 0)) { if ((remoteCTag != null) && (count == 0)) {
// If we are updating an existing installation with no saved journal, we need to add // If we are updating an existing installation with no saved journal, we need to add
remoteEntries = journal.getEntries(settings.password(), null); remoteEntries = journal.getEntries(crypto, null);
int i = 0; int i = 0;
for (JournalEntryManager.Entry entry : remoteEntries) { for (JournalEntryManager.Entry entry : remoteEntries) {
SyncEntry cEntry = SyncEntry.fromJournalEntry(settings.password(), entry); SyncEntry cEntry = SyncEntry.fromJournalEntry(crypto, entry);
persistSyncEntry(entry.getUuid(), cEntry); persistSyncEntry(entry.getUuid(), cEntry);
i++; i++;
if (remoteCTag.equals(entry.getUuid())) { if (remoteCTag.equals(entry.getUuid())) {
@ -278,7 +283,7 @@ abstract public class SyncManager {
} }
} }
} else { } else {
remoteEntries = journal.getEntries(settings.password(), remoteCTag); remoteEntries = journal.getEntries(crypto, remoteCTag);
} }
App.log.info("Fetched " + String.valueOf(remoteEntries.size()) + " entries"); App.log.info("Fetched " + String.valueOf(remoteEntries.size()) + " entries");
@ -297,7 +302,7 @@ abstract public class SyncManager {
i++; i++;
App.log.info("Processing (" + String.valueOf(i) + "/" + strTotal + ") " + entry.toString()); App.log.info("Processing (" + String.valueOf(i) + "/" + strTotal + ") " + entry.toString());
SyncEntry cEntry = SyncEntry.fromJournalEntry(settings.password(), entry); SyncEntry cEntry = SyncEntry.fromJournalEntry(crypto, entry);
App.log.info("Processing resource for journal entry"); App.log.info("Processing resource for journal entry");
processSyncEntry(cEntry); processSyncEntry(cEntry);
@ -360,7 +365,7 @@ abstract public class SyncManager {
for (LocalResource local : localDeleted) { for (LocalResource local : localDeleted) {
SyncEntry entry = new SyncEntry(local.getContent(), SyncEntry.Actions.DELETE); SyncEntry entry = new SyncEntry(local.getContent(), SyncEntry.Actions.DELETE);
JournalEntryManager.Entry tmp = new JournalEntryManager.Entry(); JournalEntryManager.Entry tmp = new JournalEntryManager.Entry();
tmp.update(settings.password(), entry.toJson(), previousEntry); tmp.update(crypto, entry.toJson(), previousEntry);
previousEntry = tmp; previousEntry = tmp;
localEntries.add(previousEntry); localEntries.add(previousEntry);
} }
@ -375,7 +380,7 @@ abstract public class SyncManager {
SyncEntry entry = new SyncEntry(local.getContent(), action); SyncEntry entry = new SyncEntry(local.getContent(), action);
JournalEntryManager.Entry tmp = new JournalEntryManager.Entry(); JournalEntryManager.Entry tmp = new JournalEntryManager.Entry();
tmp.update(settings.password(), entry.toJson(), previousEntry); tmp.update(crypto, entry.toJson(), previousEntry);
previousEntry = tmp; previousEntry = tmp;
localEntries.add(previousEntry); localEntries.add(previousEntry);
} }

@ -31,6 +31,7 @@ import com.etesync.syncadapter.App;
import com.etesync.syncadapter.HttpClient; import com.etesync.syncadapter.HttpClient;
import com.etesync.syncadapter.InvalidAccountException; import com.etesync.syncadapter.InvalidAccountException;
import com.etesync.syncadapter.R; import com.etesync.syncadapter.R;
import com.etesync.syncadapter.journalmanager.Crypto;
import com.etesync.syncadapter.journalmanager.Exceptions; import com.etesync.syncadapter.journalmanager.Exceptions;
import com.etesync.syncadapter.journalmanager.JournalManager; import com.etesync.syncadapter.journalmanager.JournalManager;
import com.etesync.syncadapter.model.CollectionInfo; import com.etesync.syncadapter.model.CollectionInfo;
@ -156,12 +157,13 @@ public class CreateCollectionFragment extends DialogFragment implements LoaderMa
JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), account), principal); JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), account), principal);
if (info.url == null) { if (info.url == null) {
// CollectionInfo doesn't have a url at this point, update it. info.url = JournalManager.Journal.genUid();
JournalManager.Journal journal = new JournalManager.Journal(settings.password(), info.toJson()); Crypto.CryptoManager crypto = new Crypto.CryptoManager(settings.password(), info.url);
JournalManager.Journal journal = new JournalManager.Journal(crypto, info.toJson(), info.url);
journalManager.putJournal(journal); journalManager.putJournal(journal);
info.url = journal.getUuid();
} else { } else {
JournalManager.Journal journal = new JournalManager.Journal(settings.password(), info.toJson(), info.url); Crypto.CryptoManager crypto = new Crypto.CryptoManager(settings.password(), info.url);
JournalManager.Journal journal = new JournalManager.Journal(crypto, info.toJson(), info.url);
journalManager.updateJournal(journal); journalManager.updateJournal(journal);
} }

@ -28,6 +28,7 @@ import com.etesync.syncadapter.App;
import com.etesync.syncadapter.HttpClient; import com.etesync.syncadapter.HttpClient;
import com.etesync.syncadapter.InvalidAccountException; import com.etesync.syncadapter.InvalidAccountException;
import com.etesync.syncadapter.R; import com.etesync.syncadapter.R;
import com.etesync.syncadapter.journalmanager.Crypto;
import com.etesync.syncadapter.journalmanager.Exceptions; import com.etesync.syncadapter.journalmanager.Exceptions;
import com.etesync.syncadapter.journalmanager.JournalManager; import com.etesync.syncadapter.journalmanager.JournalManager;
import com.etesync.syncadapter.model.CollectionInfo; import com.etesync.syncadapter.model.CollectionInfo;
@ -119,7 +120,9 @@ public class DeleteCollectionFragment extends DialogFragment implements LoaderMa
HttpUrl principal = HttpUrl.get(settings.getUri()); HttpUrl principal = HttpUrl.get(settings.getUri());
JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), account), principal); JournalManager journalManager = new JournalManager(HttpClient.create(getContext(), account), principal);
journalManager.deleteJournal(new JournalManager.Journal(settings.password(), collectionInfo.toJson(), collectionInfo.url)); Crypto.CryptoManager crypto = new Crypto.CryptoManager(settings.password(), collectionInfo.url);
journalManager.deleteJournal(new JournalManager.Journal(crypto, collectionInfo.toJson(), collectionInfo.url));
JournalEntity journalEntity = JournalEntity.fetch(data, collectionInfo.url); JournalEntity journalEntity = JournalEntity.fetch(data, collectionInfo.url);
journalEntity.setDeleted(true); journalEntity.setDeleted(true);
data.update(journalEntity); data.update(journalEntity);

Loading…
Cancel
Save