package com.frontier42.keepass.impl; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Hashtable; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.sax.SAXSource; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.bouncycastle.crypto.StreamCipher; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import com.keepassdroid.database.exception.InvalidDBException; import com.keepassdroid.database.exception.InvalidDBVersionException; import com.keepassdroid.database.exception.InvalidPasswordException; public class DatabaseDomReaderV4 extends DatabaseReaderV4 { public Document loadData(InputStream is, String password) throws IOException, InvalidDBVersionException, InvalidPasswordException, InvalidDBException { InputStream decrypted=openDecryptedStrem(is, password); return parseXmlData(decrypted, randomStream); } private Document parseXmlData(InputStream readerStream, StreamCipher cipher) throws IOException { Document doc=null; try { doc=sax2dom(new InputSource(readerStream)); XPathFactory xPathfactory = XPathFactory.newInstance(); XPath xpath = xPathfactory.newXPath(); XPathExpression expr = xpath.compile("//Entry"); NodeList entries=(NodeList) expr.evaluate(doc, XPathConstants.NODESET); Map<String, Element> hexUuidIndex=new Hashtable<String, Element>(entries.getLength()); //Index by UUID, also decrypts the password values for (int i=0; i<entries.getLength();i++){ Element entry=(Element) entries.item(i); Element uuid=(Element) entry.getElementsByTagName("UUID").item(0); byte[] uuidBytes=javax.xml.bind.DatatypeConverter.parseBase64Binary(uuid.getTextContent().trim()); String uuidHex=javax.xml.bind.DatatypeConverter.printHexBinary(uuidBytes); hexUuidIndex.put(uuidHex, entry); Map<String, Element> fieldsMap=new Hashtable<String, Element>(); entry.setUserData("fields", fieldsMap, null); NodeList fields=entry.getElementsByTagName("String"); for (int j=0; j<fields.getLength();j++){ Element field=(Element) fields.item(j); Element fieldKey=(Element) field.getElementsByTagName("Key").item(0); Element fieldValue=(Element) field.getElementsByTagName("Value").item(0); fieldsMap.put(fieldKey.getTextContent(), fieldValue); Attr attrProtected=fieldValue.getAttributeNode("Protected"); //Decrypt password if (attrProtected!=null && "true".equalsIgnoreCase(attrProtected.getValue())){ String encrypted=fieldValue.getTextContent(); 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); fieldValue.setTextContent(new String(plainBuf, "UTF-8")); fieldValue.removeAttributeNode(attrProtected); } } } //resolve all field references expr = xpath.compile("//String/Value[contains(text(), '{REF:')]"); NodeList nodes=(NodeList) expr.evaluate(doc, XPathConstants.NODESET); Pattern refRegex=Pattern.compile("\\{REF:([^@]+)@([^:]+):([^\\}]+)\\}"); StringBuffer sb=new StringBuffer(); for (int i=0; i<nodes.getLength();i++){ Element node=(Element)nodes.item(i); String rawValue=node.getTextContent(); Matcher m = refRegex.matcher(rawValue); sb.setLength(0); while(m.find()){ Element entry=null; String replacement=m.group(0); if ("I".equals(m.group(2))){ entry=hexUuidIndex.get(m.group(3)); } if (entry!=null){ Map<String, Element> fields=(Map<String, Element>) entry.getUserData("fields"); Element value=null; if ("U".equals(m.group(1))){ value=fields.get("UserName"); }else if ("P".equals(m.group(1))){ value=fields.get("Password"); } if (value!=null){ replacement=value.getTextContent(); } } //findByUUIDExpr = xpath.compile("//Entry[UUID='"+javax.xml.bind.DatatypeConverter.pa +"']"); m.appendReplacement(sb, replacement); } m.appendTail(sb); node.setTextContent(sb.toString()); } } catch (TransformerFactoryConfigurationError e) { throw new IOException(e); } catch (TransformerException e) { throw new IOException(e); } catch (XPathExpressionException e) { throw new IOException(e); } return doc; } private void dumpXmlData(InputStream readerStream) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader( readerStream)); String line = null; while ((line = reader.readLine()) != null) { System.out.println(line); } } public Document sax2dom(InputSource input) throws TransformerFactoryConfigurationError, TransformerException{ TransformerFactory tfactory=TransformerFactory.newInstance(); SAXTransformerFactory saxTFactory = (SAXTransformerFactory) tfactory; Transformer t=tfactory.newTransformer(); Source s=new SAXSource(input); DOMResult r=new DOMResult(); t.transform(s, r); return (Document) r.getNode(); } 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 { System.out.println(); while (true) { if (xpp.next() == XmlPullParser.END_DOCUMENT) break; switch (xpp.getEventType()) { case XmlPullParser.START_TAG: System.out.print("<"); System.out.print(xpp.getName()); // System.out.print(" att-count=\""+xpp.getAttributeCount()+"\""); System.out.println(">"); break; case XmlPullParser.TEXT: System.out.print(xpp.getText()); break; case XmlPullParser.END_TAG: System.out.print("</"); System.out.print(xpp.getName()); System.out.println(">"); break; default: System.err.println("EventType:" + xpp.getEventType()); break; } } } }