/** * Initializes the XML creator & parser and caches those objects in the new IdentityXML object so * that they do not have to be initialized each time an identity is exported/imported. */ public XMLTransformer(WebOfTrust myWoT) { mWoT = myWoT; mDB = mWoT.getDatabase(); try { DocumentBuilderFactory xmlFactory = DocumentBuilderFactory.newInstance(); xmlFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); // DOM parser uses .setAttribute() to pass to underlying Xerces xmlFactory.setAttribute("http://apache.org/xml/features/disallow-doctype-decl", true); mDocumentBuilder = xmlFactory.newDocumentBuilder(); mDOM = mDocumentBuilder.getDOMImplementation(); mSerializer = TransformerFactory.newInstance().newTransformer(); mSerializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); mSerializer.setOutputProperty( OutputKeys.INDENT, "yes"); // TODO: Disable as soon as bug 0004850 is fixed. mSerializer.setOutputProperty(OutputKeys.STANDALONE, "no"); mDateFormat = new SimpleDateFormat("yyyy-MM-dd"); mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); } catch (Exception e) { throw new RuntimeException(e); } }
/** * @param xmlInputStream An InputStream which must not return more than {@link * MAX_INTRODUCTIONPUZZLE_BYTE_SIZE} bytes. */ public IntroductionPuzzle importIntroductionPuzzle( FreenetURI puzzleURI, InputStream xmlInputStream) throws SAXException, IOException, InvalidParameterException, UnknownIdentityException, IllegalBase64Exception, ParseException { xmlInputStream = new OneBytePerReadInputStream( xmlInputStream); // Workaround for Java bug, see the stream class for explanation // May not be accurate by definition of available(). So the JavaDoc requires the callers to obey // the size limit, this is a double-check. if (xmlInputStream.available() > MAX_INTRODUCTIONPUZZLE_BYTE_SIZE) throw new IllegalArgumentException( "XML contains too many bytes: " + xmlInputStream.available()); String puzzleID; IntroductionPuzzle.PuzzleType puzzleType; String puzzleMimeType; Date puzzleValidUntilDate; byte[] puzzleData; Document xmlDoc; synchronized ( mDocumentBuilder) { // TODO: Figure out whether the DocumentBuilder is maybe synchronized // anyway xmlDoc = mDocumentBuilder.parse(xmlInputStream); } Element puzzleElement = (Element) xmlDoc.getElementsByTagName("IntroductionPuzzle").item(0); if (Integer.parseInt(puzzleElement.getAttribute("Version")) > INTRODUCTION_XML_FORMAT_VERSION) throw new InvalidParameterException( "Version " + puzzleElement.getAttribute("Version") + " > " + INTRODUCTION_XML_FORMAT_VERSION); puzzleID = puzzleElement.getAttribute("ID"); puzzleType = IntroductionPuzzle.PuzzleType.valueOf(puzzleElement.getAttribute("Type")); puzzleMimeType = puzzleElement.getAttribute("MimeType"); synchronized (mDateFormat) { puzzleValidUntilDate = mDateFormat.parse(puzzleElement.getAttribute("ValidUntil")); } Element dataElement = (Element) puzzleElement.getElementsByTagName("Data").item(0); puzzleData = Base64.decodeStandard(dataElement.getAttribute("Value")); IntroductionPuzzle puzzle; synchronized (mWoT) { Identity puzzleInserter = mWoT.getIdentityByURI(puzzleURI); puzzle = new IntroductionPuzzle( mWoT, puzzleInserter, puzzleID, puzzleType, puzzleMimeType, puzzleData, IntroductionPuzzle.getDateFromRequestURI(puzzleURI), puzzleValidUntilDate, IntroductionPuzzle.getIndexFromRequestURI(puzzleURI)); mWoT.getIntroductionPuzzleStore().storeAndCommit(puzzle); } return puzzle; }
/** * Creates an identity from an identity introduction, stores it in the database and returns the * new identity. If the identity already exists, the existing identity is returned. * * <p>You have to synchronize on the WebOfTrust object when using this function! TODO: Remove this * requirement and re-query the parameter OwnIdentity puzzleOwner from the database after we are * synchronized. * * @param xmlInputStream An InputStream which must not return more than {@link * MAX_INTRODUCTION_BYTE_SIZE} bytes. * @throws InvalidParameterException If the XML format is unknown or if the puzzle owner does not * allow introduction anymore. * @throws IOException * @throws SAXException */ public Identity importIntroduction(OwnIdentity puzzleOwner, InputStream xmlInputStream) throws InvalidParameterException, SAXException, IOException { xmlInputStream = new OneBytePerReadInputStream( xmlInputStream); // Workaround for Java bug, see the stream class for explanation // May not be accurate by definition of available(). So the JavaDoc requires the callers to obey // the size limit, this is a double-check. if (xmlInputStream.available() > MAX_INTRODUCTION_BYTE_SIZE) throw new IllegalArgumentException( "XML contains too many bytes: " + xmlInputStream.available()); FreenetURI identityURI; Identity newIdentity; Document xmlDoc; synchronized ( mDocumentBuilder) { // TODO: Figure out whether the DocumentBuilder is maybe synchronized // anyway xmlDoc = mDocumentBuilder.parse(xmlInputStream); } Element introductionElement = (Element) xmlDoc.getElementsByTagName("IdentityIntroduction").item(0); if (Integer.parseInt(introductionElement.getAttribute("Version")) > XML_FORMAT_VERSION) throw new InvalidParameterException( "Version " + introductionElement.getAttribute("Version") + " > " + XML_FORMAT_VERSION); Element identityElement = (Element) introductionElement.getElementsByTagName("Identity").item(0); identityURI = new FreenetURI(identityElement.getAttribute("URI")); final IdentityFetcher identityFetcher = mWoT.getIdentityFetcher(); synchronized (mWoT) { synchronized (identityFetcher) { if (!puzzleOwner.hasContext(IntroductionPuzzle.INTRODUCTION_CONTEXT)) throw new InvalidParameterException( "Trying to import an identity identroduction for an own identity which does not allow introduction."); synchronized (Persistent.transactionLock(mDB)) { try { try { newIdentity = mWoT.getIdentityByURI(identityURI); if (logMINOR) Logger.minor( this, "Imported introduction for an already existing identity: " + newIdentity); } catch (UnknownIdentityException e) { newIdentity = new Identity(mWoT, identityURI, null, false); // We do NOT call setEdition(): An attacker might solve puzzles pretending to be // someone else and publish bogus edition numbers for // that identity by that. The identity constructor only takes the edition number as // edition hint, this is the proper behavior. // TODO: As soon as we have code for signing XML with an identity SSK we could sign // the introduction XML and therefore prevent that // attack. // newIdentity.setEdition(identityURI.getEdition()); newIdentity.storeWithoutCommit(); if (logMINOR) Logger.minor(this, "Imported introduction for an unknown identity: " + newIdentity); } try { mWoT.getTrust(puzzleOwner, newIdentity); /* Double check ... */ if (logMINOR) Logger.minor(this, "The identity is already trusted."); } catch (NotTrustedException ex) { // 0 trust will not allow the import of other new identities for the new identity // because the trust list import code will only create // new identities if the score of an identity is > 0, not if it is equal to 0. mWoT.setTrustWithoutCommit( puzzleOwner, newIdentity, (byte) 0, "Trust received by solving a captcha."); } // setTrustWithoutCommit() does this for us. // identityFetcher.storeStartFetchCommandWithoutCommit(newIdentity.getID()); newIdentity.checkedCommit(this); } catch (RuntimeException error) { Persistent.checkedRollbackAndThrow(mDB, this, error); // Satisfy the compiler - without this the return at the end of the function would // complain about the uninitialized newIdentity variable throw error; } } } } return newIdentity; }
/** * Imports a identity XML file into the given web of trust. This includes: - The identity itself * and its attributes - The trust list of the identity, if it has published one in the XML. * * @param xmlInputStream The input stream containing the XML. */ public void importIdentity(FreenetURI identityURI, InputStream xmlInputStream) { try { // Catch import problems so we can mark the edition as parsing failed // We first parse the XML without synchronization, then do the synchronized import into the // WebOfTrust final ParsedIdentityXML xmlData = parseIdentityXML(xmlInputStream); synchronized (mWoT) { synchronized (mWoT.getIdentityFetcher()) { final Identity identity = mWoT.getIdentityByURI(identityURI); Logger.normal(this, "Importing parsed XML for " + identity); long newEdition = identityURI.getEdition(); if (identity.getEdition() > newEdition) { if (logDEBUG) Logger.debug( this, "Fetched an older edition: current == " + identity.getEdition() + "; fetched == " + identityURI.getEdition()); return; } else if (identity.getEdition() == newEdition) { if (identity.getCurrentEditionFetchState() == FetchState.Fetched) { if (logDEBUG) Logger.debug( this, "Fetched current edition which is marked as fetched already, not importing: " + identityURI); return; } else if (identity.getCurrentEditionFetchState() == FetchState.ParsingFailed) { Logger.normal( this, "Re-fetched current-edition which was marked as parsing failed: " + identityURI); } } // We throw parse errors AFTER checking the edition number: If this XML was outdated // anyway, we don't have to throw. if (xmlData.parseError != null) throw xmlData.parseError; synchronized (Persistent.transactionLock(mDB)) { try { // Transaction rollback block identity.setEdition( newEdition); // The identity constructor only takes the edition number as a hint, // so we must store it explicitly. boolean didPublishTrustListPreviously = identity.doesPublishTrustList(); identity.setPublishTrustList(xmlData.identityPublishesTrustList); try { identity.setNickname(xmlData.identityName); } catch (Exception e) { /* Nickname changes are not allowed, ignore them... */ Logger.warning(this, "setNickname() failed.", e); } try { /* Failure of context importing should not make an identity disappear, therefore we catch exceptions. */ identity.setContexts(xmlData.identityContexts); } catch (Exception e) { Logger.warning(this, "setContexts() failed.", e); } try { /* Failure of property importing should not make an identity disappear, therefore we catch exceptions. */ identity.setProperties(xmlData.identityProperties); } catch (Exception e) { Logger.warning(this, "setProperties() failed", e); } mWoT .beginTrustListImport(); // We delete the old list if !identityPublishesTrustList // and it did publish one earlier => we always call this. if (xmlData.identityPublishesTrustList) { // We import the trust list of an identity if it's score is equal to 0, but we only // create new identities or import edition hints // if the score is greater than 0. Solving a captcha therefore only allows you to // create one single identity. boolean positiveScore = false; boolean hasCapacity = false; // TODO: getBestScore/getBestCapacity should always yield a positive result because // we store a positive score object for an OwnIdentity // upon creation. The only case where it could not exist might be // restoreOwnIdentity() ... check that. If it is created there as well, // remove the additional check here. if (identity instanceof OwnIdentity) { // Importing of OwnIdentities is always allowed positiveScore = true; hasCapacity = true; } else { try { positiveScore = mWoT.getBestScore(identity) > 0; hasCapacity = mWoT.getBestCapacity(identity) > 0; } catch (NotInTrustTreeException e) { } } HashSet<String> identitiesWithUpdatedEditionHint = null; if (positiveScore) { identitiesWithUpdatedEditionHint = new HashSet<String>(xmlData.identityTrustList.size() * 2); } for (final ParsedIdentityXML.TrustListEntry trustListEntry : xmlData.identityTrustList) { final FreenetURI trusteeURI = trustListEntry.mTrusteeURI; final byte trustValue = trustListEntry.mTrustValue; final String trustComment = trustListEntry.mTrustComment; Identity trustee = null; try { trustee = mWoT.getIdentityByURI(trusteeURI); if (positiveScore) { if (trustee.setNewEditionHint(trusteeURI.getEdition())) { identitiesWithUpdatedEditionHint.add(trustee.getID()); trustee.storeWithoutCommit(); } } } catch (UnknownIdentityException e) { if (hasCapacity) { /* We only create trustees if the truster has capacity to rate them. */ try { trustee = new Identity(mWoT, trusteeURI, null, false); trustee.storeWithoutCommit(); } catch (MalformedURLException urlEx) { // Logging the exception does NOT log the actual malformed URL so we do it // manually. Logger.warning( this, "Received malformed identity URL: " + trusteeURI, urlEx); throw urlEx; } } } if (trustee != null) mWoT.setTrustWithoutCommit(identity, trustee, trustValue, trustComment); } for (Trust trust : mWoT.getGivenTrustsOfDifferentEdition(identity, identityURI.getEdition())) { mWoT.removeTrustWithoutCommit(trust); } IdentityFetcher identityFetcher = mWoT.getIdentityFetcher(); if (positiveScore) { for (String id : identitiesWithUpdatedEditionHint) identityFetcher.storeUpdateEditionHintCommandWithoutCommit(id); // We do not have to store fetch commands for new identities here, // setTrustWithoutCommit does it. } } else if (!xmlData.identityPublishesTrustList && didPublishTrustListPreviously && !(identity instanceof OwnIdentity)) { // If it does not publish a trust list anymore, we delete all trust values it has // given. for (Trust trust : mWoT.getGivenTrusts(identity)) mWoT.removeTrustWithoutCommit(trust); } mWoT.finishTrustListImport(); identity.onFetched(); // Marks the identity as parsed successfully identity.storeAndCommit(); } catch (Exception e) { mWoT.abortTrustListImport(e, Logger.LogLevel.WARNING); // Does the rollback throw e; } // try } // synchronized(Persistent.transactionLock(db)) Logger.normal(this, "Finished XML import for " + identity); } // synchronized(mWoT) } // synchronized(mWoT.getIdentityFetcher()) } // try catch (Exception e) { synchronized (mWoT) { synchronized (mWoT.getIdentityFetcher()) { try { final Identity identity = mWoT.getIdentityByURI(identityURI); final long newEdition = identityURI.getEdition(); if (identity.getEdition() <= newEdition) { Logger.normal(this, "Marking edition as parsing failed: " + identityURI); try { identity.setEdition(newEdition); } catch (InvalidParameterException e1) { // Would only happen if newEdition < current edition. // We have validated the opposite. throw new RuntimeException(e1); } identity.onParsingFailed(); identity.storeAndCommit(); } else { Logger.normal( this, "Not marking edition as parsing failed, we have already fetched a new one (" + identity.getEdition() + "):" + identityURI); } Logger.normal(this, "Parsing identity XML failed gracefully for " + identityURI, e); } catch (UnknownIdentityException uie) { Logger.error(this, "Fetched an unknown identity: " + identityURI); } } } } }
public void exportOwnIdentity(OwnIdentity identity, OutputStream os) throws TransformerException { Document xmlDoc; synchronized ( mDocumentBuilder) { // TODO: Figure out whether the DocumentBuilder is maybe synchronized // anyway xmlDoc = mDOM.createDocument(null, WebOfTrust.WOT_NAME, null); } // 1.0 does not support all Unicode characters which the String class supports. To prevent us // from having to filter all Strings, we use 1.1 xmlDoc.setXmlVersion("1.1"); Element rootElement = xmlDoc.getDocumentElement(); // We include the WoT version to have an easy way of handling bogus XML which might be created // by bugged versions. rootElement.setAttribute("Version", Long.toString(Version.getRealVersion())); /* Create the identity Element */ Element identityElement = xmlDoc.createElement("Identity"); identityElement.setAttribute( "Version", Integer.toString(XML_FORMAT_VERSION)); /* Version of the XML format */ synchronized (mWoT) { identityElement.setAttribute("Name", identity.getNickname()); identityElement.setAttribute( "PublishesTrustList", Boolean.toString(identity.doesPublishTrustList())); /* Create the context Elements */ for (String context : identity.getContexts()) { Element contextElement = xmlDoc.createElement("Context"); contextElement.setAttribute("Name", context); identityElement.appendChild(contextElement); } /* Create the property Elements */ for (Entry<String, String> property : identity.getProperties().entrySet()) { Element propertyElement = xmlDoc.createElement("Property"); propertyElement.setAttribute("Name", property.getKey()); propertyElement.setAttribute("Value", property.getValue()); identityElement.appendChild(propertyElement); } /* Create the trust list Element and its trust Elements */ if (identity.doesPublishTrustList()) { Element trustListElement = xmlDoc.createElement("TrustList"); int trustCount = 0; for (Trust trust : mWoT.getGivenTrustsSortedDescendingByLastSeen(identity)) { if (++trustCount > MAX_IDENTITY_XML_TRUSTEE_AMOUNT) { Logger.normal( this, "Amount of trustees exceeded " + MAX_IDENTITY_XML_TRUSTEE_AMOUNT + ", not adding any more to trust list of " + identity); break; } /* We should make very sure that we do not reveal the other own identity's */ if (trust.getTruster() != identity) throw new RuntimeException( "Error in WoT: It is trying to export trust values of someone else in the trust list " + "of " + identity + ": Trust value from " + trust.getTruster() + ""); Element trustElement = xmlDoc.createElement("Trust"); trustElement.setAttribute("Identity", trust.getTrustee().getRequestURI().toString()); trustElement.setAttribute("Value", Byte.toString(trust.getValue())); trustElement.setAttribute("Comment", trust.getComment()); trustListElement.appendChild(trustElement); } identityElement.appendChild(trustListElement); } } rootElement.appendChild(identityElement); DOMSource domSource = new DOMSource(xmlDoc); StreamResult resultStream = new StreamResult(os); synchronized ( mSerializer) { // TODO: Figure out whether the Serializer is maybe synchronized anyway mSerializer.transform(domSource, resultStream); } }