/* * Copyright 2009-2017 Brian Pellin. * * This file is part of KeePassDroid. * * KeePassDroid is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * KeePassDroid is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KeePassDroid. If not, see <http://www.gnu.org/licenses/>. * */ package com.keepassdroid.database.load; import static com.keepassdroid.database.PwDatabaseV4XML.*; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.Stack; import java.util.TimeZone; import java.util.UUID; import java.util.zip.GZIPInputStream; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import org.spongycastle.crypto.StreamCipher; import org.spongycastle.util.encoders.Base64Encoder; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import biz.source_code.base64Coder.Base64Coder; import com.keepassdroid.UpdateStatus; import com.keepassdroid.crypto.CipherFactory; import com.keepassdroid.crypto.PwStreamCipherFactory; import com.keepassdroid.crypto.engine.CipherEngine; import com.keepassdroid.database.BinaryPool; import com.keepassdroid.database.CrsAlgorithm; import com.keepassdroid.database.ITimeLogger; import com.keepassdroid.database.PwCompressionAlgorithm; import com.keepassdroid.database.PwDatabaseV4; import com.keepassdroid.database.PwDatabaseV4XML; import com.keepassdroid.database.PwDbHeaderV4; import com.keepassdroid.database.PwDeletedObject; import com.keepassdroid.database.PwEntryV4; import com.keepassdroid.database.PwGroupV4; import com.keepassdroid.database.PwIconCustom; import com.keepassdroid.database.exception.ArcFourException; import com.keepassdroid.database.exception.InvalidDBException; import com.keepassdroid.database.exception.InvalidPasswordException; import com.keepassdroid.database.security.ProtectedBinary; import com.keepassdroid.database.security.ProtectedString; import com.keepassdroid.stream.BetterCipherInputStream; import com.keepassdroid.stream.HashedBlockInputStream; import com.keepassdroid.stream.HmacBlockInputStream; import com.keepassdroid.stream.LEDataInputStream; import com.keepassdroid.utils.DateUtil; import com.keepassdroid.utils.EmptyUtils; import com.keepassdroid.utils.MemUtil; import com.keepassdroid.utils.Types; public class ImporterV4 extends Importer { private StreamCipher randomStream; private PwDatabaseV4 db; private BinaryPool binPool = new BinaryPool(); private byte[] hashOfHeader = null; private byte[] pbHeader = null; private long version; Calendar utcCal; public ImporterV4() { utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); } protected PwDatabaseV4 createDB() { return new PwDatabaseV4(); } @Override public PwDatabaseV4 openDatabase(InputStream inStream, String password, InputStream keyInputStream) throws IOException, InvalidDBException { return openDatabase(inStream, password, keyInputStream, new UpdateStatus()); } @Override public PwDatabaseV4 openDatabase(InputStream inStream, String password, InputStream keyInputStream, UpdateStatus status) throws IOException, InvalidDBException { db = createDB(); PwDbHeaderV4 header = new PwDbHeaderV4(db); PwDbHeaderV4.HeaderAndHash hh = header.loadFromFile(inStream); version = header.version; hashOfHeader = hh.hash; pbHeader = hh.header; db.setMasterKey(password, keyInputStream); db.makeFinalKey(header.masterSeed, db.kdfParameters); CipherEngine engine; Cipher cipher; try { engine = CipherFactory.getInstance(db.dataCipher); db.dataEngine = engine; cipher = engine.getCipher(Cipher.DECRYPT_MODE, db.finalKey, header.encryptionIV); } catch (NoSuchAlgorithmException e) { throw new IOException("Invalid algorithm."); } catch (NoSuchPaddingException e) { throw new IOException("Invalid algorithm."); } catch (InvalidKeyException e) { throw new IOException("Invalid algorithm."); } catch (InvalidAlgorithmParameterException e) { throw new IOException("Invalid algorithm."); } InputStream isPlain; if (version < PwDbHeaderV4.FILE_VERSION_32_4) { InputStream decrypted = AttachCipherStream(inStream, cipher); LEDataInputStream dataDecrypted = new LEDataInputStream(decrypted); byte[] storedStartBytes = null; try { storedStartBytes = dataDecrypted.readBytes(32); if (storedStartBytes == null || storedStartBytes.length != 32) { throw new InvalidPasswordException(); } } catch (IOException e) { throw new InvalidPasswordException(); } if (!Arrays.equals(storedStartBytes, header.streamStartBytes)) { throw new InvalidPasswordException(); } isPlain = new HashedBlockInputStream(dataDecrypted); } else { // KDBX 4 LEDataInputStream isData = new LEDataInputStream(inStream); byte[] storedHash = isData.readBytes(32); if (!Arrays.equals(storedHash,hashOfHeader)) { throw new InvalidDBException(); } byte[] hmacKey = db.hmacKey; byte[] headerHmac = PwDbHeaderV4.computeHeaderHmac(pbHeader, hmacKey); byte[] storedHmac = isData.readBytes(32); if (storedHmac == null || storedHmac.length != 32) { throw new InvalidDBException(); } // Mac doesn't match if (! Arrays.equals(headerHmac, storedHmac)) { throw new InvalidDBException(); } HmacBlockInputStream hmIs = new HmacBlockInputStream(isData, true, hmacKey); isPlain = AttachCipherStream(hmIs, cipher); } InputStream isXml; if ( db.compressionAlgorithm == PwCompressionAlgorithm.Gzip ) { isXml = new GZIPInputStream(isPlain); } else { isXml = isPlain; } if (version >= header.FILE_VERSION_32_4) { LoadInnerHeader(isXml, header); } if ( header.protectedStreamKey == null ) { assert(false); throw new IOException("Invalid stream key."); } randomStream = PwStreamCipherFactory.getInstance(header.innerRandomStream, header.protectedStreamKey); if ( randomStream == null ) { throw new ArcFourException(); } ReadXmlStreamed(isXml); return db; } private InputStream AttachCipherStream(InputStream is, Cipher cipher) { return new BetterCipherInputStream(is, cipher, 50 * 1024); } private void LoadInnerHeader(InputStream is, PwDbHeaderV4 header) throws IOException { LEDataInputStream lis = new LEDataInputStream(is); while(true) { if (!ReadInnerHeader(lis, header)) break; } } private boolean ReadInnerHeader(LEDataInputStream lis, PwDbHeaderV4 header) throws IOException { byte fieldId = (byte)lis.read(); int size = lis.readInt(); if (size < 0) throw new IOException("Corrupted file"); byte[] data = new byte[0]; if (size > 0) { data = lis.readBytes(size); } boolean result = true; switch(fieldId) { case PwDbHeaderV4.PwDbInnerHeaderV4Fields.EndOfHeader: result = false; break; case PwDbHeaderV4.PwDbInnerHeaderV4Fields.InnerRandomStreamID: header.setRandomStreamID(data); break; case PwDbHeaderV4.PwDbInnerHeaderV4Fields.InnerRandomstreamKey: header.protectedStreamKey = data; break; case PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary: if (data.length < 1) throw new IOException("Invalid binary format"); byte flag = data[0]; boolean prot = (flag & PwDbHeaderV4.KdbxBinaryFlags.Protected) != PwDbHeaderV4.KdbxBinaryFlags.None; byte[] bin = new byte[data.length - 1]; System.arraycopy(data, 1, bin, 0, data.length-1); ProtectedBinary pb = new ProtectedBinary(prot, bin); if (prot) { Arrays.fill(data, (byte)0); } break; default: assert(false); break; } return result; } private enum KdbContext { Null, KeePassFile, Meta, Root, MemoryProtection, CustomIcons, CustomIcon, CustomData, CustomDataItem, RootDeletedObjects, DeletedObject, Group, GroupTimes, GroupCustomData, GroupCustomDataItem, Entry, EntryTimes, EntryString, EntryBinary, EntryAutoType, EntryAutoTypeItem, EntryHistory, EntryCustomData, EntryCustomDataItem, Binaries } private static final long DEFAULT_HISTORY_DAYS = 365; private boolean readNextNode = true; private Stack<PwGroupV4> ctxGroups = new Stack<PwGroupV4>(); private PwGroupV4 ctxGroup = null; private PwEntryV4 ctxEntry = null; private String ctxStringName = null; private ProtectedString ctxStringValue = null; private String ctxBinaryName = null; private ProtectedBinary ctxBinaryValue = null; private String ctxATName = null; private String ctxATSeq = null; private boolean entryInHistory = false; private PwEntryV4 ctxHistoryBase = null; private PwDeletedObject ctxDeletedObject = null; private UUID customIconID = PwDatabaseV4.UUID_ZERO; private byte[] customIconData; private String customDataKey = null; private String customDataValue = null; private String groupCustomDataKey = null; private String groupCustomDataValue = null; private String entryCustomDataKey = null; private String entryCustomDataValue = null; private void ReadXmlStreamed(InputStream readerStream) throws IOException, InvalidDBException { try { ReadDocumentStreamed(CreatePullParser(readerStream)); } catch (XmlPullParserException e) { e.printStackTrace(); throw new IOException(e.getLocalizedMessage()); } } private static XmlPullParser CreatePullParser(InputStream readerStream) throws XmlPullParserException { XmlPullParserFactory xppf = XmlPullParserFactory.newInstance(); xppf.setNamespaceAware(false); XmlPullParser xpp = xppf.newPullParser(); xpp.setInput(readerStream, null); return xpp; } private void ReadDocumentStreamed(XmlPullParser xpp) throws XmlPullParserException, IOException, InvalidDBException { ctxGroups.clear(); KdbContext ctx = KdbContext.Null; readNextNode = true; while (true) { if ( readNextNode ) { if( xpp.next() == XmlPullParser.END_DOCUMENT ) break; } else { readNextNode = true; } switch ( xpp.getEventType() ) { case XmlPullParser.START_TAG: ctx = ReadXmlElement(ctx, xpp); break; case XmlPullParser.END_TAG: ctx = EndXmlElement(ctx, xpp); break; default: assert(false); break; } } // Error checks if ( ctx != KdbContext.Null ) throw new IOException("Malformed"); if ( ctxGroups.size() != 0 ) throw new IOException("Malformed"); } private KdbContext ReadXmlElement(KdbContext ctx, XmlPullParser xpp) throws XmlPullParserException, IOException, InvalidDBException { String name = xpp.getName(); switch (ctx) { case Null: if ( name.equalsIgnoreCase(ElemDocNode) ) { return SwitchContext(ctx, KdbContext.KeePassFile, xpp); } else ReadUnknown(xpp); break; case KeePassFile: if ( name.equalsIgnoreCase(ElemMeta) ) { return SwitchContext(ctx, KdbContext.Meta, xpp); } else if ( name.equalsIgnoreCase(ElemRoot) ) { return SwitchContext(ctx, KdbContext.Root, xpp); } else { ReadUnknown(xpp); } break; case Meta: if ( name.equalsIgnoreCase(ElemGenerator) ) { ReadString(xpp); // Ignore } else if ( name.equalsIgnoreCase(ElemHeaderHash) ) { String encodedHash = ReadString(xpp); if (!EmptyUtils.isNullOrEmpty(encodedHash) && (hashOfHeader != null)) { byte[] hash = Base64Coder.decode(encodedHash); if (!Arrays.equals(hash, hashOfHeader)) { throw new InvalidDBException(); } } } else if (name.equalsIgnoreCase(ElemSettingsChanged)) { db.settingsChanged = ReadTime(xpp); } else if ( name.equalsIgnoreCase(ElemDbName) ) { db.name = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemDbNameChanged) ) { db.nameChanged = ReadTime(xpp); } else if ( name.equalsIgnoreCase(ElemDbDesc) ) { db.description = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemDbDescChanged) ) { db.descriptionChanged = ReadTime(xpp); } else if ( name.equalsIgnoreCase(ElemDbDefaultUser) ) { db.defaultUserName = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemDbDefaultUserChanged) ) { db.defaultUserNameChanged = ReadTime(xpp); } else if ( name.equalsIgnoreCase(ElemDbColor)) { // TODO: Add support to interpret the color if we want to allow changing the database color db.color = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemDbMntncHistoryDays) ) { db.maintenanceHistoryDays = ReadUInt(xpp, DEFAULT_HISTORY_DAYS); } else if ( name.equalsIgnoreCase(ElemDbKeyChanged) ) { db.keyLastChanged = ReadTime(xpp); } else if ( name.equalsIgnoreCase(ElemDbKeyChangeRec) ) { db.keyChangeRecDays = ReadLong(xpp, -1); } else if ( name.equalsIgnoreCase(ElemDbKeyChangeForce) ) { db.keyChangeForceDays = ReadLong(xpp, -1); } else if ( name.equalsIgnoreCase(ElemDbKeyChangeForceOnce) ) { db.keyChangeForceOnce = ReadBool(xpp, false); } else if ( name.equalsIgnoreCase(ElemMemoryProt) ) { return SwitchContext(ctx, KdbContext.MemoryProtection, xpp); } else if ( name.equalsIgnoreCase(ElemCustomIcons) ) { return SwitchContext(ctx, KdbContext.CustomIcons, xpp); } else if ( name.equalsIgnoreCase(ElemRecycleBinEnabled) ) { db.recycleBinEnabled = ReadBool(xpp, true); } else if ( name.equalsIgnoreCase(ElemRecycleBinUuid) ) { db.recycleBinUUID = ReadUuid(xpp); } else if ( name.equalsIgnoreCase(ElemRecycleBinChanged) ) { db.recycleBinChanged = ReadTime(xpp); } else if ( name.equalsIgnoreCase(ElemEntryTemplatesGroup) ) { db.entryTemplatesGroup = ReadUuid(xpp); } else if ( name.equalsIgnoreCase(ElemEntryTemplatesGroupChanged) ) { db.entryTemplatesGroupChanged = ReadTime(xpp); } else if ( name.equalsIgnoreCase(ElemHistoryMaxItems) ) { db.historyMaxItems = ReadInt(xpp, -1); } else if ( name.equalsIgnoreCase(ElemHistoryMaxSize) ) { db.historyMaxSize = ReadLong(xpp, -1); } else if ( name.equalsIgnoreCase(ElemEntryTemplatesGroupChanged) ) { db.entryTemplatesGroupChanged = ReadTime(xpp); } else if ( name.equalsIgnoreCase(ElemLastSelectedGroup) ) { db.lastSelectedGroup = ReadUuid(xpp); } else if ( name.equalsIgnoreCase(ElemLastTopVisibleGroup) ) { db.lastTopVisibleGroup = ReadUuid(xpp); } else if ( name.equalsIgnoreCase(ElemBinaries) ) { return SwitchContext(ctx, KdbContext.Binaries, xpp); } else if ( name.equalsIgnoreCase(ElemCustomData) ) { return SwitchContext(ctx, KdbContext.CustomData, xpp); } break; case MemoryProtection: if ( name.equalsIgnoreCase(ElemProtTitle) ) { db.memoryProtection.protectTitle = ReadBool(xpp, false); } else if ( name.equalsIgnoreCase(ElemProtUserName) ) { db.memoryProtection.protectUserName = ReadBool(xpp, false); } else if ( name.equalsIgnoreCase(ElemProtPassword) ) { db.memoryProtection.protectPassword = ReadBool(xpp, false); } else if ( name.equalsIgnoreCase(ElemProtURL) ) { db.memoryProtection.protectUrl = ReadBool(xpp, false); } else if ( name.equalsIgnoreCase(ElemProtNotes) ) { db.memoryProtection.protectNotes = ReadBool(xpp, false); } else if ( name.equalsIgnoreCase(ElemProtAutoHide) ) { db.memoryProtection.autoEnableVisualHiding = ReadBool(xpp, false); } else { ReadUnknown(xpp); } break; case CustomIcons: if ( name.equalsIgnoreCase(ElemCustomIconItem) ) { return SwitchContext(ctx, KdbContext.CustomIcon, xpp); } else { ReadUnknown(xpp); } break; case CustomIcon: if ( name.equalsIgnoreCase(ElemCustomIconItemID) ) { customIconID = ReadUuid(xpp); } else if ( name.equalsIgnoreCase(ElemCustomIconItemData) ) { String strData = ReadString(xpp); if ( strData != null && strData.length() > 0 ) { customIconData = Base64Coder.decode(strData); } else { assert(false); } } else { ReadUnknown(xpp); } break; case Binaries: if ( name.equalsIgnoreCase(ElemBinary) ) { String key = xpp.getAttributeValue(null, AttrId); if ( key != null ) { ProtectedBinary pbData = ReadProtectedBinary(xpp); int id = Integer.parseInt(key); binPool.put(id, pbData); } else { ReadUnknown(xpp); } } else { ReadUnknown(xpp); } break; case CustomData: if ( name.equalsIgnoreCase(ElemStringDictExItem) ) { return SwitchContext(ctx, KdbContext.CustomDataItem, xpp); } else { ReadUnknown(xpp); } break; case CustomDataItem: if ( name.equalsIgnoreCase(ElemKey) ) { customDataKey = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemValue) ) { customDataValue = ReadString(xpp); } else { ReadUnknown(xpp); } break; case Root: if ( name.equalsIgnoreCase(ElemGroup) ) { assert(ctxGroups.size() == 0); if ( ctxGroups.size() != 0 ) throw new IOException("Group list should be empty."); db.rootGroup = new PwGroupV4(); ctxGroups.push((PwGroupV4)db.rootGroup); ctxGroup = ctxGroups.peek(); return SwitchContext(ctx, KdbContext.Group, xpp); } else if ( name.equalsIgnoreCase(ElemDeletedObjects) ) { return SwitchContext(ctx, KdbContext.RootDeletedObjects, xpp); } else { ReadUnknown(xpp); } break; case Group: if ( name.equalsIgnoreCase(ElemUuid) ) { ctxGroup.uuid = ReadUuid(xpp); } else if ( name.equalsIgnoreCase(ElemName) ) { ctxGroup.name = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemNotes) ) { ctxGroup.notes = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemIcon) ) { ctxGroup.icon = db.iconFactory.getIcon((int)ReadUInt(xpp, 0)); } else if ( name.equalsIgnoreCase(ElemCustomIconID) ) { ctxGroup.customIcon = db.iconFactory.getIcon(ReadUuid(xpp)); } else if ( name.equalsIgnoreCase(ElemTimes) ) { return SwitchContext(ctx, KdbContext.GroupTimes, xpp); } else if ( name.equalsIgnoreCase(ElemIsExpanded) ) { ctxGroup.isExpanded = ReadBool(xpp, true); } else if ( name.equalsIgnoreCase(ElemGroupDefaultAutoTypeSeq) ) { ctxGroup.defaultAutoTypeSequence = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemEnableAutoType) ) { ctxGroup.enableAutoType = StringToBoolean(ReadString(xpp)); } else if ( name.equalsIgnoreCase(ElemEnableSearching) ) { ctxGroup.enableSearching = StringToBoolean(ReadString(xpp)); } else if ( name.equalsIgnoreCase(ElemLastTopVisibleEntry) ) { ctxGroup.lastTopVisibleEntry = ReadUuid(xpp); } else if ( name.equalsIgnoreCase(ElemCustomData) ) { return SwitchContext(ctx, KdbContext.GroupCustomData, xpp); } else if ( name.equalsIgnoreCase(ElemGroup) ) { ctxGroup = new PwGroupV4(); ctxGroups.peek().AddGroup(ctxGroup, true); ctxGroups.push(ctxGroup); return SwitchContext(ctx, KdbContext.Group, xpp); } else if ( name.equalsIgnoreCase(ElemEntry) ) { ctxEntry = new PwEntryV4(); ctxGroup.AddEntry(ctxEntry, true); entryInHistory = false; return SwitchContext(ctx, KdbContext.Entry, xpp); } else { ReadUnknown(xpp); } break; case GroupCustomData: if (name.equalsIgnoreCase(ElemStringDictExItem)) { return SwitchContext(ctx, KdbContext.GroupCustomDataItem, xpp); } else { ReadUnknown(xpp); } break; case GroupCustomDataItem: if (name.equalsIgnoreCase(ElemKey)) { groupCustomDataKey = ReadString(xpp); } else if (name.equalsIgnoreCase(ElemValue)) { groupCustomDataValue = ReadString(xpp); } else { ReadUnknown(xpp); } break; case Entry: if ( name.equalsIgnoreCase(ElemUuid) ) { ctxEntry.setUUID(ReadUuid(xpp)); } else if ( name.equalsIgnoreCase(ElemIcon) ) { ctxEntry.icon = db.iconFactory.getIcon((int)ReadUInt(xpp, 0)); } else if ( name.equalsIgnoreCase(ElemCustomIconID) ) { ctxEntry.customIcon = db.iconFactory.getIcon(ReadUuid(xpp)); } else if ( name.equalsIgnoreCase(ElemFgColor) ) { ctxEntry.foregroundColor = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemBgColor) ) { ctxEntry.backgroupColor = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemOverrideUrl) ) { ctxEntry.overrideURL = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemTags) ) { ctxEntry.tags = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemTimes) ) { return SwitchContext(ctx, KdbContext.EntryTimes, xpp); } else if ( name.equalsIgnoreCase(ElemString) ) { return SwitchContext(ctx, KdbContext.EntryString, xpp); } else if ( name.equalsIgnoreCase(ElemBinary) ) { return SwitchContext(ctx, KdbContext.EntryBinary, xpp); } else if ( name.equalsIgnoreCase(ElemAutoType) ) { return SwitchContext(ctx, KdbContext.EntryAutoType, xpp); } else if ( name.equalsIgnoreCase(ElemCustomData)) { return SwitchContext(ctx, KdbContext.EntryCustomData, xpp); } else if ( name.equalsIgnoreCase(ElemHistory) ) { assert(!entryInHistory); if ( ! entryInHistory ) { ctxHistoryBase = ctxEntry; return SwitchContext(ctx, KdbContext.EntryHistory, xpp); } else { ReadUnknown(xpp); } } else { ReadUnknown(xpp); } break; case EntryCustomData: if (name.equalsIgnoreCase(ElemStringDictExItem)) { return SwitchContext(ctx, KdbContext.EntryCustomDataItem, xpp); } else { ReadUnknown(xpp); } break; case EntryCustomDataItem: if (name.equalsIgnoreCase(ElemKey)) { entryCustomDataKey = ReadString(xpp); } else if (name.equalsIgnoreCase(ElemValue)) { entryCustomDataValue = ReadString(xpp); } else { ReadUnknown(xpp); } break; case GroupTimes: case EntryTimes: ITimeLogger tl; if ( ctx == KdbContext.GroupTimes ) { tl = ctxGroup; } else { tl = ctxEntry; } if ( name.equalsIgnoreCase(ElemLastModTime) ) { tl.setLastModificationTime(ReadTime(xpp)); } else if ( name.equalsIgnoreCase(ElemCreationTime) ) { tl.setCreationTime(ReadTime(xpp)); } else if ( name.equalsIgnoreCase(ElemLastAccessTime) ) { tl.setLastAccessTime(ReadTime(xpp)); } else if ( name.equalsIgnoreCase(ElemExpiryTime) ) { tl.setExpiryTime(ReadTime(xpp)); } else if ( name.equalsIgnoreCase(ElemExpires) ) { tl.setExpires(ReadBool(xpp, false)); } else if ( name.equalsIgnoreCase(ElemUsageCount) ) { tl.setUsageCount(ReadULong(xpp, 0)); } else if ( name.equalsIgnoreCase(ElemLocationChanged) ) { tl.setLocationChanged(ReadTime(xpp)); } else { ReadUnknown(xpp); } break; case EntryString: if ( name.equalsIgnoreCase(ElemKey) ) { ctxStringName = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemValue) ) { ctxStringValue = ReadProtectedString(xpp); } else { ReadUnknown(xpp); } break; case EntryBinary: if ( name.equalsIgnoreCase(ElemKey) ) { ctxBinaryName = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemValue) ) { ctxBinaryValue = ReadProtectedBinary(xpp); } break; case EntryAutoType: if ( name.equalsIgnoreCase(ElemAutoTypeEnabled) ) { ctxEntry.autoType.enabled = ReadBool(xpp, true); } else if ( name.equalsIgnoreCase(ElemAutoTypeObfuscation) ) { ctxEntry.autoType.obfuscationOptions = ReadUInt(xpp, 0); } else if ( name.equalsIgnoreCase(ElemAutoTypeDefaultSeq) ) { ctxEntry.autoType.defaultSequence = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemAutoTypeItem) ) { return SwitchContext(ctx, KdbContext.EntryAutoTypeItem, xpp); } else { ReadUnknown(xpp); } break; case EntryAutoTypeItem: if ( name.equalsIgnoreCase(ElemWindow) ) { ctxATName = ReadString(xpp); } else if ( name.equalsIgnoreCase(ElemKeystrokeSequence) ) { ctxATSeq = ReadString(xpp); } else { ReadUnknown(xpp); } break; case EntryHistory: if ( name.equalsIgnoreCase(ElemEntry) ) { ctxEntry = new PwEntryV4(); ctxHistoryBase.history.add(ctxEntry); entryInHistory = true; return SwitchContext(ctx, KdbContext.Entry, xpp); } else { ReadUnknown(xpp); } break; case RootDeletedObjects: if ( name.equalsIgnoreCase(ElemDeletedObject) ) { ctxDeletedObject = new PwDeletedObject(); db.deletedObjects.add(ctxDeletedObject); return SwitchContext(ctx, KdbContext.DeletedObject, xpp); } else { ReadUnknown(xpp); } break; case DeletedObject: if ( name.equalsIgnoreCase(ElemUuid) ) { ctxDeletedObject.uuid = ReadUuid(xpp); } else if ( name.equalsIgnoreCase(ElemDeletionTime) ) { ctxDeletedObject.setDeletionTime(ReadTime(xpp)); } else { ReadUnknown(xpp); } break; default: ReadUnknown(xpp); break; } return ctx; } private KdbContext EndXmlElement(KdbContext ctx, XmlPullParser xpp) throws XmlPullParserException { assert(xpp.getEventType() == XmlPullParser.END_TAG); String name = xpp.getName(); if ( ctx == KdbContext.KeePassFile && name.equalsIgnoreCase(ElemDocNode) ) { return KdbContext.Null; } else if ( ctx == KdbContext.Meta && name.equalsIgnoreCase(ElemMeta) ) { return KdbContext.KeePassFile; } else if ( ctx == KdbContext.Root && name.equalsIgnoreCase(ElemRoot) ) { return KdbContext.KeePassFile; } else if ( ctx == KdbContext.MemoryProtection && name.equalsIgnoreCase(ElemMemoryProt) ) { return KdbContext.Meta; } else if ( ctx == KdbContext.CustomIcons && name.equalsIgnoreCase(ElemCustomIcons) ) { return KdbContext.Meta; } else if ( ctx == KdbContext.CustomIcon && name.equalsIgnoreCase(ElemCustomIconItem) ) { if ( ! customIconID.equals(PwDatabaseV4.UUID_ZERO) ) { PwIconCustom icon = new PwIconCustom(customIconID, customIconData); db.customIcons.add(icon); db.iconFactory.put(icon); } else assert(false); customIconID = PwDatabaseV4.UUID_ZERO; customIconData = null; return KdbContext.CustomIcons; } else if ( ctx == KdbContext.Binaries && name.equalsIgnoreCase(ElemBinaries) ) { return KdbContext.Meta; } else if ( ctx == KdbContext.CustomData && name.equalsIgnoreCase(ElemCustomData) ) { return KdbContext.Meta; } else if ( ctx == KdbContext.CustomDataItem && name.equalsIgnoreCase(ElemStringDictExItem) ) { if ( customDataKey != null && customDataValue != null) { db.customData.put(customDataKey, customDataValue); } else assert(false); customDataKey = null; customDataValue = null; return KdbContext.CustomData; } else if ( ctx == KdbContext.Group && name.equalsIgnoreCase(ElemGroup) ) { if ( ctxGroup.uuid == null || ctxGroup.uuid.equals(PwDatabaseV4.UUID_ZERO) ) { ctxGroup.uuid = UUID.randomUUID(); } ctxGroups.pop(); if ( ctxGroups.size() == 0 ) { ctxGroup = null; return KdbContext.Root; } else { ctxGroup = ctxGroups.peek(); return KdbContext.Group; } } else if ( ctx == KdbContext.GroupTimes && name.equalsIgnoreCase(ElemTimes) ) { return KdbContext.Group; } else if ( ctx == KdbContext.GroupCustomData && name.equalsIgnoreCase(ElemCustomData) ) { return KdbContext.Group; } else if ( ctx == KdbContext.GroupCustomDataItem && name.equalsIgnoreCase(ElemStringDictExItem)) { if (groupCustomDataKey != null && groupCustomDataValue != null) { ctxGroup.customData.put(groupCustomDataKey, groupCustomDataKey); } else { assert(false); } groupCustomDataKey = null; groupCustomDataValue = null; return KdbContext.GroupCustomData; } else if ( ctx == KdbContext.Entry && name.equalsIgnoreCase(ElemEntry) ) { if ( ctxEntry.uuid == null || ctxEntry.uuid.equals(PwDatabaseV4.UUID_ZERO) ) { ctxEntry.uuid = UUID.randomUUID(); } if ( entryInHistory ) { ctxEntry = ctxHistoryBase; return KdbContext.EntryHistory; } return KdbContext.Group; } else if ( ctx == KdbContext.EntryTimes && name.equalsIgnoreCase(ElemTimes) ) { return KdbContext.Entry; } else if ( ctx == KdbContext.EntryString && name.equalsIgnoreCase(ElemString) ) { ctxEntry.strings.put(ctxStringName, ctxStringValue); ctxStringName = null; ctxStringValue = null; return KdbContext.Entry; } else if ( ctx == KdbContext.EntryBinary && name.equalsIgnoreCase(ElemBinary) ) { ctxEntry.binaries.put(ctxBinaryName, ctxBinaryValue); ctxBinaryName = null; ctxBinaryValue = null; return KdbContext.Entry; } else if ( ctx == KdbContext.EntryAutoType && name.equalsIgnoreCase(ElemAutoType) ) { return KdbContext.Entry; } else if ( ctx == KdbContext.EntryAutoTypeItem && name.equalsIgnoreCase(ElemAutoTypeItem) ) { ctxEntry.autoType.put(ctxATName, ctxATSeq); ctxATName = null; ctxATSeq = null; return KdbContext.EntryAutoType; } else if ( ctx == KdbContext.EntryCustomData && name.equalsIgnoreCase(ElemCustomData)) { return KdbContext.Entry; } else if ( ctx == KdbContext.EntryCustomDataItem && name.equalsIgnoreCase(ElemStringDictExItem)) { if (entryCustomDataKey != null && entryCustomDataValue != null) { ctxEntry.customData.put(entryCustomDataKey, entryCustomDataValue); } else { assert(false); } entryCustomDataKey = null; entryCustomDataValue = null; return KdbContext.EntryCustomData; } else if ( ctx == KdbContext.EntryHistory && name.equalsIgnoreCase(ElemHistory) ) { entryInHistory = false; return KdbContext.Entry; } else if ( ctx == KdbContext.RootDeletedObjects && name.equalsIgnoreCase(ElemDeletedObjects) ) { return KdbContext.Root; } else if ( ctx == KdbContext.DeletedObject && name.equalsIgnoreCase(ElemDeletedObject) ) { ctxDeletedObject = null; return KdbContext.RootDeletedObjects; } else { assert(false); String contextName = ""; if (ctx != null) { contextName = ctx.name(); } throw new RuntimeException("Invalid end element: Context " + contextName + "End element: " + name); } } private Date ReadTime(XmlPullParser xpp) throws IOException, XmlPullParserException { String sDate = ReadString(xpp); Date utcDate = null; if (version >= PwDbHeaderV4.FILE_VERSION_32_4) { byte[] buf = Base64Coder.decode(sDate); if (buf.length != 8) { byte[] buf8 = new byte[8]; System.arraycopy(buf, 0, buf8, 0, buf.length); buf = buf8; } long seconds = LEDataInputStream.readLong(buf, 0); utcDate = DateUtil.convertKDBX4Time(seconds); } else { try { utcDate = PwDatabaseV4XML.dateFormat.parse(sDate); } catch (ParseException e) { // Catch with null test below } if (utcDate == null) { utcDate = new Date(0L); } } return utcDate; } private void ReadUnknown(XmlPullParser xpp) throws XmlPullParserException, IOException { assert(false); if ( xpp.isEmptyElementTag() ) return; String unknownName = xpp.getName(); ProcessNode(xpp); while (xpp.next() != XmlPullParser.END_DOCUMENT ) { if ( xpp.getEventType() == XmlPullParser.END_TAG ) break; if ( xpp.getEventType() == XmlPullParser.START_TAG ) continue; ReadUnknown(xpp); } assert(xpp.getName() == unknownName); } private boolean ReadBool(XmlPullParser xpp, boolean bDefault) throws IOException, XmlPullParserException { String str = ReadString(xpp); if ( str.equalsIgnoreCase("true") ) { return true; } else if ( str.equalsIgnoreCase("false") ) { return false; } else { return bDefault; } } private UUID ReadUuid(XmlPullParser xpp) throws IOException, XmlPullParserException { String encoded = ReadString(xpp); if (encoded == null || encoded.length() == 0 ) { return PwDatabaseV4.UUID_ZERO; } // TODO: Switch to framework Base64 once API level 8 is the minimum byte[] buf = Base64Coder.decode(encoded); return Types.bytestoUUID(buf); } private int ReadInt(XmlPullParser xpp, int def) throws IOException, XmlPullParserException { String str = ReadString(xpp); int u; try { u = Integer.parseInt(str); } catch( NumberFormatException e) { u = def; } return u; } private static final long MAX_UINT = 4294967296L; // 2^32 private long ReadUInt(XmlPullParser xpp, long uDefault) throws IOException, XmlPullParserException { long u; u = ReadULong(xpp, uDefault); if ( u < 0 || u > MAX_UINT ) { throw new NumberFormatException("Outside of the uint size"); } return u; } private long ReadLong(XmlPullParser xpp, long def) throws IOException, XmlPullParserException { String str = ReadString(xpp); long u; try { u = Long.parseLong(str); } catch( NumberFormatException e) { u = def; } return u; } private long ReadULong(XmlPullParser xpp, long uDefault) throws IOException, XmlPullParserException { long u = ReadLong(xpp, uDefault); if ( u < 0 ) { u = uDefault; } return u; } private ProtectedString ReadProtectedString(XmlPullParser xpp) throws XmlPullParserException, IOException { byte[] buf = ProcessNode(xpp); if ( buf != null) { try { return new ProtectedString(true, new String(buf, "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); throw new IOException(e.getLocalizedMessage()); } } return new ProtectedString(false, ReadString(xpp)); } private ProtectedBinary ReadProtectedBinary(XmlPullParser xpp) throws XmlPullParserException, IOException { String ref = xpp.getAttributeValue(null, AttrRef); if (ref != null) { xpp.next(); // Consume end tag int id = Integer.parseInt(ref); return binPool.get(id); } boolean compressed = false; String comp = xpp.getAttributeValue(null, AttrCompressed); if (comp != null) { compressed = comp.equalsIgnoreCase(ValTrue); } byte[] buf = ProcessNode(xpp); if ( buf != null ) return new ProtectedBinary(true, buf); String base64 = ReadString(xpp); if ( base64.length() == 0 ) return ProtectedBinary.EMPTY; byte[] data = Base64Coder.decode(base64); if (compressed) { data = MemUtil.decompress(data); } return new ProtectedBinary(false, data); } private String ReadString(XmlPullParser xpp) throws IOException, XmlPullParserException { byte[] buf = ProcessNode(xpp); if ( buf != null ) { try { return new String(buf, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new IOException(e.getLocalizedMessage()); } } //readNextNode = false; return xpp.nextText(); } private String ReadStringRaw(XmlPullParser xpp) throws XmlPullParserException, IOException { //readNextNode = false; return xpp.nextText(); } private byte[] ProcessNode(XmlPullParser xpp) throws XmlPullParserException, IOException { assert(xpp.getEventType() == XmlPullParser.START_TAG); byte[] buf = null; if ( xpp.getAttributeCount() > 0 ) { String protect = xpp.getAttributeValue(null, AttrProtected); if ( protect != null && protect.equalsIgnoreCase(ValTrue) ) { String encrypted = ReadStringRaw(xpp); if ( encrypted.length() > 0 ) { buf = Base64Coder.decode(encrypted); byte[] plainText = new byte[buf.length]; randomStream.processBytes(buf, 0, buf.length, plainText, 0); return plainText; } else { buf = new byte[0]; } } } return buf; } private KdbContext SwitchContext(KdbContext ctxCurrent, KdbContext ctxNew, XmlPullParser xpp) throws XmlPullParserException, IOException { if ( xpp.isEmptyElementTag() ) { xpp.next(); // Consume the end tag return ctxCurrent; } return ctxNew; } private Boolean StringToBoolean(String str) { if ( str == null || str.length() == 0 ) { return null; } String trimmed = str.trim(); if ( trimmed.equalsIgnoreCase("true") ) { return true; } else if ( trimmed.equalsIgnoreCase("false") ) { return false; } return null; } }