[onionoo/master] Accept hashed fingerprints in search and lookup URLs.

commit b32605525e737559ba1f501a63dd69e27336a513 Author: Karsten Loesing <karsten.loesing@gmx.net> Date: Mon Mar 19 15:23:28 2012 +0100 Accept hashed fingerprints in search and lookup URLs. More precisely, accept hashed relay fingerprints and hashed hashed bridge fingerprints. The problem addressed here is that both */lookup/ and */search/ URLs require a non-hashed fingerprint to look up a relay and a hashed fingerprint to look up a bridge. This works fine if a) users know how to hash fingerprints and b) the client knows whether the user provided a hashed or non-hashed fingerprint. But what if either a) or b) is not the case? If the client sees a 40-character fingerprint it shouldn't simply submit it to the server. It might be a non-hashed bridge fingerprint. The change made here is to make */lookup/ and */search/ URLs accept hashed relay fingerprints and hashed hashed bridge fingerprints. Clients can now be advised to always hash any 40-character hex input they receive from users and include that in their request to the server. If the user provides a non-hashed fingerprint of a bridge the client won't reveal the non-hashed fingerprint in the URL. If the user provides a hashed fingerprint, looking up the hash of the hash still works and returns the bridge the user was looking for. Implements #5368. --- src/org/torproject/onionoo/ResourceServlet.java | 161 +++++++++++++++-------- web/index.html | 36 ++++- 2 files changed, 134 insertions(+), 63 deletions(-) diff --git a/src/org/torproject/onionoo/ResourceServlet.java b/src/org/torproject/onionoo/ResourceServlet.java index b28eca1..e11c7ac 100644 --- a/src/org/torproject/onionoo/ResourceServlet.java +++ b/src/org/torproject/onionoo/ResourceServlet.java @@ -8,8 +8,10 @@ import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.regex.Pattern; @@ -18,6 +20,10 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.DigestUtils; + public class ResourceServlet extends HttpServlet { private static final long serialVersionUID = 7236658979947465319L; @@ -31,6 +37,9 @@ public class ResourceServlet extends HttpServlet { private String relaysPublishedLine = null, bridgesPublishedLine = null; private List<String> relayLines = new ArrayList<String>(), bridgeLines = new ArrayList<String>(); + private Map<String, String> + relayFingerprintSummaryLines = new HashMap<String, String>(), + bridgeFingerprintSummaryLines = new HashMap<String, String>(); private void readSummaryFile() { File summaryFile = new File("/srv/onionoo/out/summary.json"); if (!summaryFile.exists()) { @@ -40,6 +49,8 @@ public class ResourceServlet extends HttpServlet { if (summaryFile.lastModified() > this.summaryFileLastModified) { this.relayLines.clear(); this.bridgeLines.clear(); + this.relayFingerprintSummaryLines.clear(); + this.bridgeFingerprintSummaryLines.clear(); try { BufferedReader br = new BufferedReader(new FileReader( summaryFile)); @@ -53,16 +64,43 @@ public class ResourceServlet extends HttpServlet { } else if (line.startsWith("\"relays\":")) { while ((line = br.readLine()) != null && !line.equals("],")) { this.relayLines.add(line); + int fingerprintStart = line.indexOf("\"f\":\""); + if (fingerprintStart > 0) { + fingerprintStart += "\"f\":\"".length(); + String fingerprint = line.substring(fingerprintStart, + fingerprintStart + 40); + String hashedFingerprint = DigestUtils.shaHex( + Hex.decodeHex(fingerprint.toCharArray())). + toUpperCase(); + this.relayFingerprintSummaryLines.put(fingerprint, line); + this.relayFingerprintSummaryLines.put(hashedFingerprint, + line); + } } } else if (line.startsWith("\"bridges\":")) { while ((line = br.readLine()) != null && !line.equals("]}")) { this.bridgeLines.add(line); + int hashedFingerprintStart = line.indexOf("\"h\":\""); + if (hashedFingerprintStart > 0) { + hashedFingerprintStart += "\"h\":\"".length(); + String hashedFingerprint = line.substring( + hashedFingerprintStart, hashedFingerprintStart + 40); + String hashedHashedFingerprint = DigestUtils.shaHex( + Hex.decodeHex(hashedFingerprint.toCharArray())). + toUpperCase(); + this.bridgeFingerprintSummaryLines.put(hashedFingerprint, + line); + this.bridgeFingerprintSummaryLines.put( + hashedHashedFingerprint, line); + } } } } br.close(); } catch (IOException e) { return; + } catch (DecoderException e) { + return; } } this.summaryFileLastModified = summaryFile.lastModified(); @@ -222,56 +260,61 @@ public class ResourceServlet extends HttpServlet { private void writeMatchingRelays(PrintWriter pw, String searchTerm, String resourceType) { - pw.print("\"relays\":["); - int written = 0; - for (String line : this.relayLines) { - boolean lineMatches = false; - if (searchTerm.startsWith("$")) { - /* Search is for $-prefixed fingerprint. */ - if (line.contains("\"f\":\"" - + searchTerm.substring(1).toUpperCase())) { - /* $-prefixed fingerprint matches. */ + if (searchTerm.length() == 40) { + Set<String> fingerprints = new HashSet<String>(); + fingerprints.add(searchTerm); + this.writeRelaysWithFingerprints(pw, fingerprints, resourceType); + } else { + pw.print("\"relays\":["); + int written = 0; + for (String line : this.relayLines) { + boolean lineMatches = false; + if (searchTerm.startsWith("$")) { + /* Search is for $-prefixed fingerprint. */ + if (line.contains("\"f\":\"" + + searchTerm.substring(1).toUpperCase())) { + /* $-prefixed fingerprint matches. */ + lineMatches = true; + } + } else if (line.toLowerCase().contains("\"n\":\"" + + searchTerm.toLowerCase())) { + /* Nickname matches. */ + lineMatches = true; + } else if ("unnamed".startsWith(searchTerm.toLowerCase()) && + line.startsWith("{\"f\":")) { + /* Nickname "Unnamed" matches. */ + lineMatches = true; + } else if (line.contains("\"f\":\"" + searchTerm.toUpperCase())) { + /* Non-$-prefixed fingerprint matches. */ + lineMatches = true; + } else if (line.substring(line.indexOf("\"a\":[")).contains("\"" + + searchTerm.toLowerCase())) { + /* Address matches. */ lineMatches = true; } - } else if (line.toLowerCase().contains("\"n\":\"" - + searchTerm.toLowerCase())) { - /* Nickname matches. */ - lineMatches = true; - } else if ("unnamed".startsWith(searchTerm.toLowerCase()) && - line.startsWith("{\"f\":")) { - /* Nickname "Unnamed" matches. */ - lineMatches = true; - } else if (line.contains("\"f\":\"" + searchTerm.toUpperCase())) { - /* Non-$-prefixed fingerprint matches. */ - lineMatches = true; - } else if (line.substring(line.indexOf("\"a\":[")).contains("\"" - + searchTerm.toLowerCase())) { - /* Address matches. */ - lineMatches = true; - } - if (lineMatches) { - String lines = this.getFromSummaryLine(line, resourceType); - if (lines.length() > 0) { - pw.print((written++ > 0 ? ",\n" : "\n") + lines); + if (lineMatches) { + String lines = this.getFromSummaryLine(line, resourceType); + if (lines.length() > 0) { + pw.print((written++ > 0 ? ",\n" : "\n") + lines); + } } } + pw.print("\n],\n"); } - pw.print("\n],\n"); } private void writeRelaysWithFingerprints(PrintWriter pw, Set<String> fingerprints, String resourceType) { pw.print("\"relays\":["); int written = 0; - for (String line : this.relayLines) { - for (String fingerprint : fingerprints) { - if (line.contains("\"f\":\"" + fingerprint.toUpperCase() - + "\",")) { - String lines = this.getFromSummaryLine(line, resourceType); - if (lines.length() > 0) { - pw.print((written++ > 0 ? ",\n" : "\n") + lines); - } - break; + for (String fingerprint : fingerprints) { + if (this.relayFingerprintSummaryLines.containsKey( + fingerprint.toUpperCase())) { + String summaryLine = this.relayFingerprintSummaryLines.get( + fingerprint.toUpperCase()); + String lines = this.getFromSummaryLine(summaryLine, resourceType); + if (lines.length() > 0) { + pw.print((written++ > 0 ? ",\n" : "\n") + lines); } } } @@ -314,33 +357,37 @@ public class ResourceServlet extends HttpServlet { if (searchTerm.startsWith("$")) { searchTerm = searchTerm.substring(1); } - pw.print("\"bridges\":["); - int written = 0; - for (String line : this.bridgeLines) { - if (line.contains("\"h\":\"" + searchTerm.toUpperCase())) { - String lines = this.getFromSummaryLine(line, resourceType); - if (lines.length() > 0) { - pw.print((written++ > 0 ? ",\n" : "\n") + lines); + if (searchTerm.length() == 40) { + Set<String> fingerprints = new HashSet<String>(); + fingerprints.add(searchTerm); + this.writeBridgesWithFingerprints(pw, fingerprints, resourceType); + } else { + pw.print("\"bridges\":["); + int written = 0; + for (String line : this.bridgeLines) { + if (line.contains("\"h\":\"" + searchTerm.toUpperCase())) { + String lines = this.getFromSummaryLine(line, resourceType); + if (lines.length() > 0) { + pw.print((written++ > 0 ? ",\n" : "\n") + lines); + } } } + pw.print("\n]}\n"); } - pw.print("\n]}\n"); } private void writeBridgesWithFingerprints(PrintWriter pw, Set<String> fingerprints, String resourceType) { pw.print("\"bridges\":["); int written = 0; - for (String line : this.bridgeLines) { - for (String fingerprint : fingerprints) { - if (line.contains("\"h\":\"" + fingerprint.toUpperCase() - + "\",")) { - String lines = this.getFromSummaryLine(line, - resourceType); - if (lines.length() > 0) { - pw.print((written++ > 0 ? ",\n" : "\n") + lines); - } - break; + for (String fingerprint : fingerprints) { + if (this.bridgeFingerprintSummaryLines.containsKey( + fingerprint.toUpperCase())) { + String summaryLine = this.bridgeFingerprintSummaryLines.get( + fingerprint.toUpperCase()); + String lines = this.getFromSummaryLine(summaryLine, resourceType); + if (lines.length() > 0) { + pw.print((written++ > 0 ? ",\n" : "\n") + lines); } } } diff --git a/web/index.html b/web/index.html index 4e63c28..8533962 100755 --- a/web/index.html +++ b/web/index.html @@ -118,12 +118,20 @@ $-prefixed)</font> fingerprint, IP address, or <font color="blue">(possibly $-prefixed)</font> hashed fingerprint. Added the option to search for $-prefixed (hashed) fingerprints on February 25, 2012. -Searches are case-insensitive.</td> +Searches are case-insensitive. +<font color="blue">Full fingerprints should always be hashed using SHA-1, +regardless of searching for a relay or a bridge, in order to not +accidentally leak non-hashed bridge fingerprints in the URL.</font> +Added the option to search for hashed fingerprints on March 19, 2012.</td> </tr> <tr> <td><b>GET summary/lookup/<i>:fingerprint</i></b></td> <td>Return the summary of the relay or bridge with <i>:fingerprint</i> -matching the fingerprint or hashed fingerprint.</td> +matching the fingerprint or hashed fingerprint. +<font color="blue">Fingerprints should always be hashed using SHA-1, +regardless of looking up a relay or a bridge, in order to not accidentally +leak non-hashed bridge fingerprints in the URL.</font> +Added the option to look up hashed fingerprints on March 19, 2012.</td> </tr> </table> <br> @@ -329,12 +337,20 @@ $-prefixed)</font> fingerprint, IP address, or <font color="blue">(possibly $-prefixed)</font> hashed fingerprint. Added the option to search for $-prefixed (hashed) fingerprints on February 25, 2012. -Searches are case-insensitive.</td> +Searches are case-insensitive. +<font color="blue">Full fingerprints should always be hashed using SHA-1, +regardless of searching for a relay or a bridge, in order to not +accidentally leak non-hashed bridge fingerprints in the URL.</font> +Added the option to search for hashed fingerprints on March 19, 2012.</td> </tr> <tr> <td><b>GET details/lookup/<i>:fingerprint</i></b></td> <td>Return the details of the relay or bridge with <i>:fingerprint</i> -matching the fingerprint or hashed fingerprint.</td> +matching the fingerprint or hashed fingerprint. +<font color="blue">Fingerprints should always be hashed using SHA-1, +regardless of looking up a relay or a bridge, in order to not accidentally +leak non-hashed bridge fingerprints in the URL.</font> +Added the option to look up hashed fingerprints on March 19, 2012.</td> </tr> </table> <br> @@ -467,12 +483,20 @@ color="blue">(possibly $-prefixed)</font> fingerprint, IP address, or <font color="blue">(possibly $-prefixed)</font> hashed fingerprint. Added the option to search for $-prefixed (hashed) fingerprints on February 25, 2012. -Searches are case-insensitive.</td> +Searches are case-insensitive. +<font color="blue">Full fingerprints should always be hashed using SHA-1, +regardless of searching for a relay or a bridge, in order to not +accidentally leak non-hashed bridge fingerprints in the URL.</font> +Added the option to search for hashed fingerprints on March 19, 2012.</td> </tr> <tr> <td><b>GET bandwidth/lookup/<i>:fingerprint</i></b></td> <td>Return the bandwidth document of the relay or bridge with -<i>:fingerprint</i> matching the fingerprint or hashed fingerprint.</td> +<i>:fingerprint</i> matching the fingerprint or hashed fingerprint. +<font color="blue">Fingerprints should always be hashed using SHA-1, +regardless of looking up a relay or a bridge, in order to not accidentally +leak non-hashed bridge fingerprints in the URL.</font> +Added the option to look up hashed fingerprints on March 19, 2012.</td> </tr> </table> </body>
participants (1)
-
karsten@torproject.org