You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Aegis/app/src/main/java/me/impy/aegis/db/DatabaseFile.java

176 lines
5.2 KiB
Java

package me.impy.aegis.db;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Arrays;
import me.impy.aegis.crypto.CryptParameters;
import me.impy.aegis.db.slots.SlotCollection;
import me.impy.aegis.crypto.CryptoUtils;
import me.impy.aegis.util.LittleByteBuffer;
public class DatabaseFile {
private static final byte SECTION_ENCRYPTION_PARAMETERS = 0x00;
private static final byte SECTION_SLOTS = 0x01;
private static final byte SECTION_END = (byte) 0xFF;
private static final byte VERSION = 1;
private final byte[] HEADER;
private byte[] _content;
private CryptParameters _cryptParameters;
private SlotCollection _slots;
public DatabaseFile() {
try {
HEADER = "AEGIS".getBytes("US_ASCII");
} catch (Exception e) {
throw new UndeclaredThrowableException(e);
}
_slots = new SlotCollection();
}
public byte[] serialize() throws IOException {
byte[] content = getContent();
CryptParameters cryptParams = getCryptParameters();
// this is dumb, java doesn't provide an endianness-aware data stream
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
DataOutputStream stream = new DataOutputStream(byteStream);
stream.write(HEADER);
stream.write(VERSION);
if (cryptParams != null) {
LittleByteBuffer paramBuffer = LittleByteBuffer.allocate(CryptoUtils.CRYPTO_NONCE_SIZE + CryptoUtils.CRYPTO_TAG_SIZE);
paramBuffer.put(cryptParams.Nonce);
paramBuffer.put(cryptParams.Tag);
writeSection(stream, SECTION_ENCRYPTION_PARAMETERS, paramBuffer.array());
}
if (!_slots.isEmpty()) {
byte[] bytes = SlotCollection.serialize(_slots);
writeSection(stream, SECTION_SLOTS, bytes);
}
writeSection(stream, SECTION_END, null);
stream.write(content);
return byteStream.toByteArray();
}
public void deserialize(byte[] data) throws Exception {
LittleByteBuffer buffer = LittleByteBuffer.wrap(data);
byte[] header = new byte[HEADER.length];
buffer.get(header);
if (!Arrays.equals(header, HEADER)) {
throw new Exception("Bad header");
}
// TODO: support different version deserialization providers
byte version = buffer.get();
if (version != VERSION) {
throw new Exception("Unsupported version");
}
CryptParameters cryptParams = null;
SlotCollection slots = new SlotCollection();
for (section s = readSection(buffer); s.ID != SECTION_END; s = readSection(buffer)) {
LittleByteBuffer sBuff = LittleByteBuffer.wrap(s.Data);
switch (s.ID) {
case SECTION_ENCRYPTION_PARAMETERS:
assertLength(s.Data, CryptoUtils.CRYPTO_NONCE_SIZE + CryptoUtils.CRYPTO_TAG_SIZE);
byte[] nonce = new byte[CryptoUtils.CRYPTO_NONCE_SIZE];
byte[] tag = new byte[CryptoUtils.CRYPTO_TAG_SIZE];
sBuff.get(nonce);
sBuff.get(tag);
cryptParams = new CryptParameters() {{
Nonce = nonce;
Tag = tag;
}};
break;
case SECTION_SLOTS:
slots = SlotCollection.deserialize(s.Data);
break;
}
}
setCryptParameters(cryptParams);
setSlots(slots);
byte[] content = new byte[buffer.remaining()];
buffer.get(content);
setContent(content);
}
public boolean isEncrypted() {
return !_slots.isEmpty() && _cryptParameters != null;
}
private static void writeSection(DataOutputStream stream, byte id, byte[] data) throws IOException {
stream.write(id);
LittleByteBuffer buffer = LittleByteBuffer.allocate(/* sizeof uint32_t */ 4);
if (data == null) {
buffer.putInt(0);
} else {
buffer.putInt(data.length);
}
stream.write(buffer.array());
if (data != null) {
stream.write(data);
}
}
private static section readSection(LittleByteBuffer buffer) {
section s = new section();
s.ID = buffer.get();
int len = buffer.getInt();
s.Data = new byte[len];
buffer.get(s.Data);
return s;
}
private static void assertLength(byte[] bytes, int length) throws Exception {
if (bytes.length != length) {
throw new Exception("Bad length");
}
}
public byte[] getContent() {
return _content;
}
public void setContent(byte[] content) {
_content = content;
}
public CryptParameters getCryptParameters() {
return _cryptParameters;
}
public void setCryptParameters(CryptParameters parameters) {
_cryptParameters = parameters;
}
public SlotCollection getSlots() {
return _slots;
}
public void setSlots(SlotCollection slots) {
_slots = slots;
}
private static class section {
byte ID;
byte[] Data;
}
}