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

karsten at torproject.org karsten at torproject.org
Mon Mar 19 14:36:40 UTC 2012


commit b32605525e737559ba1f501a63dd69e27336a513
Author: Karsten Loesing <karsten.loesing at 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>



More information about the tor-commits mailing list