package com.frontier42.keepass.ant; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.Hashtable; import java.util.Map; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamReader; import org.bouncycastle.crypto.StreamCipher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.frontier42.keepass.KeepassDatabase; import com.frontier42.keepass.KeepassDatabaseFactory; import com.frontier42.keepass.KeepassEntry; import com.frontier42.keepass.KeepassGroup; import com.frontier42.keepass.impl.DatabaseReaderV4; public class KeepassStreamReader { protected StreamCipher randomStream; private static final String EL_UUID = "UUID"; private final Logger LOG=LoggerFactory.getLogger(getClass()); private static final String EL_VALUE = "Value"; private static final String EL_NAME = "Name"; private static final String EL_ROOT = "Root"; private static final String EL_GROUP = "Group"; private static final String EL_ENTRY = "Entry"; private static final String EL_HISTORY = "History"; private static final String EL_PASSWORD = "Password"; private static final String EL_USER_NAME = "UserName"; private static final String EL_TITLE = "Title"; private static final String EL_STRING = "String"; private static final String EL_KEY = "Key"; /* public static class SAXHandler extends DefaultHandler { } public void testReadAsSAX() throws Exception { SAXParserFactory parserFactor = SAXParserFactory.newInstance(); SAXParser parser = parserFactor.newSAXParser(); SAXHandler handler = new SAXHandler(); parser.parse(ClassLoader.getSystemResourceAsStream("xml/employee.xml"), handler); } */ public String readCharacters(XMLStreamReader reader, String element) throws Exception { while (reader.hasNext()) { int event = reader.next(); switch (event) { case XMLStreamConstants.START_ELEMENT: if (element.equals(reader.getLocalName())) { return readCharacters(reader); } break; case XMLStreamConstants.END_ELEMENT: if (element.equals(reader.getLocalName())) { return null; } break; } } return null; } private static class ElementText{ private String _text; private Map<String, String> attributes=new Hashtable<String, String>(); ElementText(){ } public void setText(String text) { this._text = text; } public String getText() { return _text; } public void setAttribute(String name, String value){ this.attributes.put(name, value); } public String getAttribute(String name){ return this.attributes.get(name); } public Map<String, String> getAttributes() { return attributes; } } public ElementText readCharactersWithAtt(XMLStreamReader reader, String element) throws Exception { while (reader.hasNext()) { int event = reader.next(); switch (event) { case XMLStreamConstants.START_ELEMENT: if (element.equals(reader.getLocalName())) { ElementText el=new ElementText(); for (int i=0;i<reader.getAttributeCount();i++){ el.setAttribute(reader.getAttributeLocalName(i), reader.getAttributeValue(i)); } el.setText(reader.getElementText()); return el; } break; case XMLStreamConstants.END_ELEMENT: if (element.equals(reader.getLocalName())) { return null; } break; } } return null; } public void skipHistory(XMLStreamReader reader, KeepassEntry entry) throws Exception { while (reader.hasNext()) { int event = reader.next(); switch (event) { case XMLStreamConstants.START_ELEMENT: String localName = reader.getLocalName(); switch (localName) { case EL_VALUE: if ("True".equalsIgnoreCase(reader.getAttributeValue(null, "Protected"))){ decryptField(reader.getElementText()); } break; } case XMLStreamConstants.END_ELEMENT: if (EL_HISTORY.equals(reader.getLocalName())){ return; } } } } public String decryptField(String encrypted) throws UnsupportedEncodingException{ byte[] buf = javax.xml.bind.DatatypeConverter.parseBase64Binary(encrypted); byte[] plainBuf = new byte[buf.length]; //System.out.println("raw:"+node.getTextContent()); randomStream.processBytes(buf, 0, buf.length, plainBuf, 0); return new String(plainBuf, "UTF-8"); } public void readEntry(XMLStreamReader reader, KeepassEntry entry) throws Exception { while (reader.hasNext()) { int event = reader.next(); switch (event) { case XMLStreamConstants.START_ELEMENT: String localName = reader.getLocalName(); switch (localName) { case EL_STRING: String key = readCharacters(reader, EL_KEY); ElementText value = readCharactersWithAtt(reader, EL_VALUE); if (EL_TITLE.equals(key)) { LOG.info("Reading Entry:"+value.getText()); entry.setTitle(value.getText()); } else if (EL_USER_NAME.equals(key)) { entry.setUsername(entry.createValue(value.getText())); } else if (EL_PASSWORD.equals(key)) { if ("True".equalsIgnoreCase(value.getAttribute("Protected"))){ entry.setPassword(entry.createValue(decryptField(value.getText()))); }else{ entry.setPassword(entry.createValue(value.getText())); } } break; case EL_UUID: String rawUUID=readCharacters(reader); byte[] uuidBytes=javax.xml.bind.DatatypeConverter.parseBase64Binary(rawUUID.trim()); String uuidHex=javax.xml.bind.DatatypeConverter.printHexBinary(uuidBytes); entry.setUUID(uuidHex); break; case EL_HISTORY: skipHistory(reader, entry); break; } break; case XMLStreamConstants.END_ELEMENT: if (EL_ENTRY.equals(reader.getLocalName())){ return; } } } } public String readCharacters(XMLStreamReader reader) throws Exception { StringBuilder builder = new StringBuilder(); while (reader.hasNext()) { int event = reader.next(); switch (event) { case XMLStreamConstants.CHARACTERS: builder.append(reader.getText().trim()); break; default: return builder.toString(); } } return builder.toString(); } public void readGroup(XMLStreamReader reader, KeepassGroup group) throws Exception { while (reader.hasNext()) { int event = reader.next(); switch (event) { case XMLStreamConstants.START_ELEMENT: String localName = reader.getLocalName(); switch (localName) { case EL_NAME: group.setName(readCharacters(reader)); LOG.info("Reading group:"+group.getName()); break; case EL_ENTRY: KeepassEntry entry = group.newEntry(); readEntry(reader, entry); group.add(entry); break; case EL_GROUP: KeepassGroup subgroup = group.newGroup(); readGroup(reader, subgroup); group.add(subgroup); break; } break; case XMLStreamConstants.END_ELEMENT: if (EL_GROUP.equals(reader.getLocalName())) { return; } break; } } } public void readRoot(XMLStreamReader reader, KeepassDatabase db) throws Exception { while (reader.hasNext()) { int event = reader.next(); switch (event) { case XMLStreamConstants.START_ELEMENT: if (EL_GROUP.equals(reader.getLocalName())) { KeepassGroup group = db.newGroup(); readGroup(reader, group); db.add(group); db.setRootGroup(group); } break; case XMLStreamConstants.END_ELEMENT: if (EL_ROOT.equals(reader.getLocalName())) { return; } } } } public KeepassDatabase load(InputStream encryptedStream, String password) throws Exception { XMLInputFactory factory = XMLInputFactory.newInstance(); DatabaseReaderV4 kdbxReader=new DatabaseReaderV4(); InputStream stream = KeepassDatabaseFactory.openDecryptedStrem(kdbxReader, encryptedStream, password); XMLStreamReader reader = factory.createXMLStreamReader(stream); this.randomStream=kdbxReader.getRandomStreamCipher(); KeepassDatabase db=new KeepassDatabase(); while (reader.hasNext()) { int event = reader.next(); switch (event) { case XMLStreamConstants.START_DOCUMENT: break; case XMLStreamConstants.END_DOCUMENT: break; case XMLStreamConstants.START_ELEMENT: if (EL_ROOT.equals(reader.getLocalName())) { readRoot(reader, db); } break; case XMLStreamConstants.CHARACTERS: break; case XMLStreamConstants.END_ELEMENT: break; } } return db; } }