Esempio n. 1
0
  /**
   * Check whether a fingerprint with all the given details, including matching SampleID, is already
   * inside the database.
   *
   * @param conn
   * @param fingerprint
   * @return
   * @throws SQLException
   */
  private static int getSampleOccurrences(Connection conn, Fingerprint fingerprint)
      throws SQLException {
    /*
     * We have seen this user before. Check if their fingerprint has changed.
     */
    String query =
        "SELECT COUNT(*) FROM `Samples` WHERE"
            + " `UserAgent`"
            + (fingerprint.getUser_agent() == null ? " IS NULL" : " = ?")
            + " AND `AcceptHeaders`"
            + (fingerprint.getAccept_headers() == null ? " IS NULL" : " = ?")
            + " AND `Platform`"
            + (fingerprint.getPlatform() == null ? " IS NULL" : " = ?")
            + " AND `PlatformFlash`"
            + (fingerprint.getPlatformFlash() == null ? " IS NULL" : " = ?")
            + " AND `PluginDetails`"
            + (fingerprint.getPluginDetails() == null ? " IS NULL" : " = ?")
            + " AND `TimeZone`"
            + (fingerprint.getTimeZone() == null ? " IS NULL" : " = ?")
            + " AND `ScreenDetails`"
            + (fingerprint.getScreenDetails() == null ? " IS NULL" : " = ?")
            + " AND `ScreenDetailsFlash`"
            + (fingerprint.getScreenDetailsFlash() == null ? " IS NULL" : " = ?")
            + " AND `LanguageFlash`"
            + (fingerprint.getLanguageFlash() == null ? " IS NULL" : " = ?")
            + " AND `Fonts`"
            + (fingerprint.getFonts() == null ? " IS NULL" : " = ?")
            + " AND `CookiesEnabled` = ?"
            + " AND `SuperCookie`"
            + (fingerprint.getSuperCookie() == null ? " IS NULL" : " = ?")
            + " AND `DoNotTrack`"
            + (fingerprint.getDoNotTrack() == null ? " IS NULL" : " = ?")
            + " AND `ClockDifference`"
            + (fingerprint.getClockDifference() == null ? " IS NULL" : " = ?")
            + " AND `DateTime`"
            + (fingerprint.getDateTime() == null ? " IS NULL" : " = ?")
            + " AND `MathTan`"
            + (fingerprint.getMathTan() == null ? " IS NULL" : " = ?")
            + " AND `UsingTor` = ?"
            + " AND `AdsBlocked`"
            + (fingerprint.getAdsBlocked() == null ? " IS NULL" : " = ?")
            + " AND `Canvas`"
            + (fingerprint.getCanvas() == null ? " IS NULL" : " = ?")
            + " AND `WebGLVendor`"
            + (fingerprint.getWebGLVendor() == null ? " IS NULL" : " = ?")
            + " AND `WebGLRenderer`"
            + (fingerprint.getWebGLRenderer() == null ? " IS NULL" : " = ?")
            + ";";
    PreparedStatement checkExists = conn.prepareStatement(query);

    int index = 1;
    if (fingerprint.getUser_agent() != null) {
      checkExists.setString(index, fingerprint.getUser_agent());
      ++index;
    }
    if (fingerprint.getAccept_headers() != null) {
      checkExists.setString(index, fingerprint.getAccept_headers());
      ++index;
    }
    if (fingerprint.getPlatform() != null) {
      checkExists.setString(index, fingerprint.getPlatform());
      ++index;
    }
    if (fingerprint.getPlatformFlash() != null) {
      checkExists.setString(index, fingerprint.getPlatformFlash());
      ++index;
    }
    if (fingerprint.getPluginDetails() != null) {
      checkExists.setString(index, fingerprint.getPluginDetails());
      ++index;
    }
    if (fingerprint.getTimeZone() != null) {
      checkExists.setInt(index, fingerprint.getTimeZone());
      ++index;
    }
    if (fingerprint.getScreenDetails() != null) {
      checkExists.setString(index, fingerprint.getScreenDetails());
      ++index;
    }
    if (fingerprint.getScreenDetailsFlash() != null) {
      checkExists.setString(index, fingerprint.getScreenDetailsFlash());
      ++index;
    }
    if (fingerprint.getLanguageFlash() != null) {
      checkExists.setString(index, fingerprint.getLanguageFlash());
      ++index;
    }
    if (fingerprint.getFonts() != null) {
      checkExists.setString(index, fingerprint.getFonts());
      ++index;
    }
    checkExists.setBoolean(index, fingerprint.isCookiesEnabled());
    ++index;
    if (fingerprint.getSuperCookie() != null) {
      checkExists.setString(index, fingerprint.getSuperCookie());
      ++index;
    }
    if (fingerprint.getDoNotTrack() != null) {
      checkExists.setString(index, fingerprint.getDoNotTrack());
      ++index;
    }
    if (fingerprint.getClockDifference() != null) {
      checkExists.setLong(index, fingerprint.getClockDifference());
      ++index;
    }
    if (fingerprint.getDateTime() != null) {
      checkExists.setString(index, fingerprint.getDateTime());
      ++index;
    }
    if (fingerprint.getMathTan() != null) {
      checkExists.setString(index, fingerprint.getMathTan());
      ++index;
    }
    checkExists.setBoolean(index, fingerprint.isUsingTor());
    ++index;
    if (fingerprint.getAdsBlocked() != null) {
      checkExists.setBoolean(index, fingerprint.getAdsBlocked());
      ++index;
    }
    if (fingerprint.getCanvas() != null) {
      checkExists.setString(index, fingerprint.getCanvas());
      ++index;
    }
    if (fingerprint.getWebGLVendor() != null) {
      checkExists.setString(index, fingerprint.getWebGLVendor());
      ++index;
    }
    if (fingerprint.getWebGLRenderer() != null) {
      checkExists.setString(index, fingerprint.getWebGLRenderer());
      ++index;
    }

    ResultSet rs = checkExists.executeQuery();

    rs.next();
    int count = rs.getInt(1);
    rs.close();
    checkExists.close();
    return count;
  }
Esempio n. 2
0
  /**
   * Insert a sample into the Samples database.
   *
   * @param conn
   * @param fingerprint
   * @return The sample ID of the inserted sample.
   * @throws SQLException
   */
  private static Integer insertSample(Connection conn, Fingerprint fingerprint)
      throws SQLException {
    PreparedStatement insertSample =
        conn.prepareStatement(insertSampleStr, Statement.RETURN_GENERATED_KEYS);
    int index = 1;
    insertSample.setString(index, fingerprint.getIpAddress());
    ++index;
    insertSample.setString(index, fingerprint.getUser_agent());
    ++index;
    insertSample.setString(index, fingerprint.getAccept_headers());
    ++index;
    insertSample.setString(index, fingerprint.getPlatform());
    ++index;
    insertSample.setString(index, fingerprint.getPlatformFlash());
    ++index;
    insertSample.setString(index, fingerprint.getPluginDetails());
    ++index;
    if (fingerprint.getTimeZone() != null) {
      insertSample.setInt(index, fingerprint.getTimeZone());
    } else {
      insertSample.setNull(index, java.sql.Types.INTEGER);
    }
    ++index;
    insertSample.setString(index, fingerprint.getScreenDetails());
    ++index;
    insertSample.setString(index, fingerprint.getScreenDetailsFlash());
    ++index;
    insertSample.setString(index, fingerprint.getLanguageFlash());
    ++index;
    insertSample.setString(index, fingerprint.getFonts());
    ++index;
    insertSample.setBoolean(index, fingerprint.isCookiesEnabled());
    ++index;
    insertSample.setString(index, fingerprint.getSuperCookie());
    ++index;
    insertSample.setString(index, fingerprint.getDoNotTrack());
    ++index;
    if (fingerprint.getClockDifference() != null) {
      insertSample.setLong(index, fingerprint.getClockDifference());
    } else {
      insertSample.setNull(index, java.sql.Types.BIGINT);
    }
    ++index;
    insertSample.setString(index, fingerprint.getDateTime());
    ++index;
    insertSample.setString(index, fingerprint.getMathTan());
    ++index;
    insertSample.setBoolean(index, fingerprint.isUsingTor());
    ++index;
    if (fingerprint.getAdsBlocked() != null) {
      insertSample.setBoolean(index, fingerprint.getAdsBlocked());
    } else {
      insertSample.setNull(index, java.sql.Types.BOOLEAN);
    }
    ++index;
    insertSample.setString(index, fingerprint.getCanvas());
    ++index;
    insertSample.setString(index, fingerprint.getWebGLVendor());
    ++index;
    insertSample.setString(index, fingerprint.getWebGLRenderer());

    insertSample.execute();

    ResultSet rs = insertSample.getGeneratedKeys();
    Integer sampleID = null;
    if (rs.next()) {
      sampleID = rs.getInt(1);
    }
    rs.close();
    insertSample.close();
    return sampleID;
  }
Esempio n. 3
0
  /**
   * Returns the sampleID of the matching sample if we've seen this sample (with SampleSetID)
   * before. Otherwise returns null.
   *
   * @param conn
   * @param fingerprint
   * @return
   * @throws SQLException
   */
  private static Integer checkSampleChanged(Connection conn, Fingerprint fingerprint)
      throws SQLException {
    if (fingerprint.getSampleSetID() == null) {
      /*
       * We know we haven't seen this sample before because there's no SampleSetID.
       */
      return null;
    }

    /*
     * We have seen this user before. Check if their fingerprint has changed.
     */
    String query =
        "SELECT `Samples`.`SampleID` FROM `SampleSets` INNER JOIN `Samples` ON `SampleSets`.`SampleID` = `Samples`.`SampleID` WHERE `SampleSetID` = ?"
            + " AND `UserAgent`"
            + (fingerprint.getUser_agent() == null ? " IS NULL" : " = ?")
            + " AND `AcceptHeaders`"
            + (fingerprint.getAccept_headers() == null ? " IS NULL" : " = ?")
            + " AND `Platform`"
            + (fingerprint.getPlatform() == null ? " IS NULL" : " = ?")
            + " AND `PlatformFlash`"
            + (fingerprint.getPlatformFlash() == null ? " IS NULL" : " = ?")
            + " AND `PluginDetails`"
            + (fingerprint.getPluginDetails() == null ? " IS NULL" : " = ?")
            + " AND `TimeZone`"
            + (fingerprint.getTimeZone() == null ? " IS NULL" : " = ?")
            + " AND `ScreenDetails`"
            + (fingerprint.getScreenDetails() == null ? " IS NULL" : " = ?")
            + " AND `ScreenDetailsFlash`"
            + (fingerprint.getScreenDetailsFlash() == null ? " IS NULL" : " = ?")
            + " AND `LanguageFlash`"
            + (fingerprint.getLanguageFlash() == null ? " IS NULL" : " = ?")
            + " AND `Fonts`"
            + (fingerprint.getFonts() == null ? " IS NULL" : " = ?")
            + " AND `CookiesEnabled` = ?"
            + " AND `SuperCookie`"
            + (fingerprint.getSuperCookie() == null ? " IS NULL" : " = ?")
            + " AND `DoNotTrack`"
            + (fingerprint.getDoNotTrack() == null ? " IS NULL" : " = ?")
            + " AND `ClockDifference`"
            + (fingerprint.getClockDifference() == null ? " IS NULL" : " = ?")
            + " AND `DateTime`"
            + (fingerprint.getDateTime() == null ? " IS NULL" : " = ?")
            + " AND `MathTan`"
            + (fingerprint.getMathTan() == null ? " IS NULL" : " = ?")
            + " AND `UsingTor` = ?"
            + " AND `AdsBlocked`"
            + (fingerprint.getAdsBlocked() == null ? " IS NULL" : " = ?")
            + " AND `Canvas`"
            + (fingerprint.getCanvas() == null ? " IS NULL" : " = ?")
            + " AND `WebGLVendor`"
            + (fingerprint.getWebGLVendor() == null ? " IS NULL" : " = ?")
            + " AND `WebGLRenderer`"
            + (fingerprint.getWebGLRenderer() == null ? " IS NULL" : " = ?")
            + ";";
    PreparedStatement checkExists = conn.prepareStatement(query);

    int index = 1;
    checkExists.setInt(index, fingerprint.getSampleSetID());
    ++index;

    if (fingerprint.getUser_agent() != null) {
      checkExists.setString(index, fingerprint.getUser_agent());
      ++index;
    }
    if (fingerprint.getAccept_headers() != null) {
      checkExists.setString(index, fingerprint.getAccept_headers());
      ++index;
    }
    if (fingerprint.getPlatform() != null) {
      checkExists.setString(index, fingerprint.getPlatform());
      ++index;
    }
    if (fingerprint.getPlatformFlash() != null) {
      checkExists.setString(index, fingerprint.getPlatformFlash());
      ++index;
    }
    if (fingerprint.getPluginDetails() != null) {
      checkExists.setString(index, fingerprint.getPluginDetails());
      ++index;
    }
    if (fingerprint.getTimeZone() != null) {
      checkExists.setInt(index, fingerprint.getTimeZone());
      ++index;
    }
    if (fingerprint.getScreenDetails() != null) {
      checkExists.setString(index, fingerprint.getScreenDetails());
      ++index;
    }
    if (fingerprint.getScreenDetailsFlash() != null) {
      checkExists.setString(index, fingerprint.getScreenDetailsFlash());
      ++index;
    }
    if (fingerprint.getLanguageFlash() != null) {
      checkExists.setString(index, fingerprint.getLanguageFlash());
      ++index;
    }
    if (fingerprint.getFonts() != null) {
      checkExists.setString(index, fingerprint.getFonts());
      ++index;
    }
    checkExists.setBoolean(index, fingerprint.isCookiesEnabled());
    ++index;
    if (fingerprint.getSuperCookie() != null) {
      checkExists.setString(index, fingerprint.getSuperCookie());
      ++index;
    }
    if (fingerprint.getDoNotTrack() != null) {
      checkExists.setString(index, fingerprint.getDoNotTrack());
      ++index;
    }
    if (fingerprint.getClockDifference() != null) {
      checkExists.setLong(index, fingerprint.getClockDifference());
      ++index;
    }
    if (fingerprint.getDateTime() != null) {
      checkExists.setString(index, fingerprint.getDateTime());
      ++index;
    }
    if (fingerprint.getMathTan() != null) {
      checkExists.setString(index, fingerprint.getMathTan());
      ++index;
    }
    checkExists.setBoolean(index, fingerprint.isUsingTor());
    ++index;
    if (fingerprint.getAdsBlocked() != null) {
      checkExists.setBoolean(index, fingerprint.getAdsBlocked());
      ++index;
    }
    if (fingerprint.getCanvas() != null) {
      checkExists.setString(index, fingerprint.getCanvas());
      ++index;
    }
    if (fingerprint.getWebGLVendor() != null) {
      checkExists.setString(index, fingerprint.getWebGLVendor());
      ++index;
    }
    if (fingerprint.getWebGLRenderer() != null) {
      checkExists.setString(index, fingerprint.getWebGLRenderer());
      ++index;
    }

    ResultSet rs = checkExists.executeQuery();

    Integer sampleID = null;
    if (rs.next()) {
      /*
       * We've seen this sample before and the fingerprint hasn't changed,
       * don't log it.
       */
      sampleID = rs.getInt(1);
    }
    rs.close();
    checkExists.close();
    return sampleID;
  }
Esempio n. 4
0
  public static final Integer processFingerprint(
      Fingerprint fingerprint, CharacteristicsBean chrsbean, UniquenessBean uniquenessbean) {
    Connection conn = null;
    try {
      conn = Database.getConnection();
      conn.setReadOnly(false);

      /*
       * Check if we've seen this sample before.
       */
      Integer sampleID = checkSampleChanged(conn, fingerprint);

      if (sampleID == null) {
        /*
         * We haven't seen this sample before.
         * Record it.
         */
        sampleID = insertSample(conn, fingerprint);

        /*
         * Insert SampleID into SampleSets table.
         */
        insertSampleSet(conn, fingerprint, sampleID);

        /*
         * Save statistics of the fingerprint.
         */
        StatisticsDAO.saveStatistics(sampleID, fingerprint);
      }

      /*
       * Get number of samples.
       */
      int sampleCount = getSampleCount(conn);

      /*
       * Get uniqueness.
       */
      int sampleOccurrences = getSampleOccurrences(conn, fingerprint);
      if (sampleOccurrences == 1) {
        uniquenessbean.setUnique(true);
      } else {
        uniquenessbean.setUnique(false);
      }
      uniquenessbean.setNum_samples(sampleCount);
      uniquenessbean.setInX(((double) sampleCount) / ((double) sampleOccurrences));
      uniquenessbean.setBits(Math.abs(Math.log(uniquenessbean.getInX()) / Math.log(2)));

      /*
       * Get each characteristic.
       */
      ArrayList<CharacteristicBean> characteristics = chrsbean.getCharacteristics();
      {
        CharacteristicBean bean =
            getCharacteristicBean(conn, sampleCount, "UserAgent", fingerprint.getUser_agent());
        bean.setName("User Agent");
        bean.setNameHoverText("The User-Agent header sent with the HTTP request for the page.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(
                conn, sampleCount, "AcceptHeaders", fingerprint.getAccept_headers());
        bean.setName("HTTP_ACCEPT Headers");
        bean.setNameHoverText(
            "The concatenation of three headers from the HTTP request:"
                + " The Accept request header, the Accept-Encoding request header, and the Accept-Language request header.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(conn, sampleCount, "Platform", fingerprint.getPlatform());
        bean.setName("Platform (JavaScript)");
        bean.setNameHoverText(
            "The name of the platform the browser is running on, detected using JavaScript.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(
                conn, sampleCount, "PlatformFlash", fingerprint.getPlatformFlash());
        if (bean.getValue().equals(NO_JAVASCRIPT)) {
          bean.setValue(NO_FLASH);
        }
        bean.setName("Platform (Flash)");
        bean.setNameHoverText(
            "The name of the platform the browser is running on, detected using Flash.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(
                conn, sampleCount, "PluginDetails", fingerprint.getPluginDetails());
        bean.setName("Browser Plugin Details");
        bean.setNameHoverText(
            "A list of the browsers installed plugins as detected using JavaScript.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(conn, sampleCount, "TimeZone", fingerprint.getTimeZone());
        bean.setName("Time Zone");
        bean.setNameHoverText("The time-zone configured on the client's machine.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(
                conn, sampleCount, "ScreenDetails", fingerprint.getScreenDetails());
        bean.setName("Screen Size and Color Depth");
        bean.setNameHoverText(
            "The screen size and colour depth of the monitor displaying the client's web browser.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(
                conn, sampleCount, "ScreenDetailsFlash", fingerprint.getScreenDetailsFlash());
        if (bean.getValue().equals(NO_JAVASCRIPT)) {
          bean.setValue(NO_FLASH);
        }
        bean.setName("Screen Size (Flash)");
        bean.setNameHoverText(
            "The resolution of the client's monitor(s)."
                + " Different from the other screen size test in that this number can be the cumulative resolution of the monitors in multiple monitor set ups.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(
                conn, sampleCount, "LanguageFlash", fingerprint.getLanguageFlash());
        if (bean.getValue().equals(NO_JAVASCRIPT)) {
          bean.setValue(NO_FLASH);
        }
        bean.setName("Language (Flash)");
        bean.setNameHoverText("The language of the client's browser, as detected using Flash.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(conn, sampleCount, "Fonts", fingerprint.getFonts());
        if (bean.getValue().equals("")) {
          bean.setValue("No fonts detected");
        } else if (bean.getValue().equals(NO_JAVASCRIPT)) {
          bean.setValue(NO_FLASH);
        }
        bean.setName("System Fonts");
        bean.setNameHoverText("The fonts installed on the client's machine, detected using Flash.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(
                conn, sampleCount, "CookiesEnabled", fingerprint.isCookiesEnabled());
        bean.setName("Are Cookies Enabled?");
        bean.setNameHoverText("Whether cookies are enabled.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(conn, sampleCount, "SuperCookie", fingerprint.getSuperCookie());
        bean.setName("Limited supercookie test");
        bean.setNameHoverText(
            "Three tests of whether DOM storage is supported (and enabled) in the client's web browser."
                + " Tests for localStorage, sessionStorage, and Internet Explorer's userData.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(conn, sampleCount, "DoNotTrack", fingerprint.getDoNotTrack());
        if (bean.getValue().equals(NO_JAVASCRIPT)) {
          bean.setValue("No preference");
        }
        bean.setName("Do Not Track header");
        bean.setNameHoverText("The value of the DNT (Do Not Track) header from the HTTP request.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(
                conn, sampleCount, "ClockDifference", fingerprint.getClockDifference());
        bean.setName("Client/server time difference (minutes)");
        bean.setNameHoverText(
            "The approximate amount of difference between the time on the client's computer and the clock on the server."
                + " i.e., the clock on the client's computer is 5 minutes ahead of the clock on the server.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(conn, sampleCount, "DateTime", fingerprint.getDateTime());
        bean.setName("Date/Time format");
        bean.setNameHoverText(
            "When the JavaScript function toLocaleString() is called on a date it can reveal information about the language of the browser via the names of days and months."
                + " For instance the output 'Thursday January 01, 10:30:00 GMT+1030 1970' reveals that English is our configured language because 'Thursday' is English."
                + " Additionally different browsers tend to return differently formatted results."
                + " For instance Opera returns the above whereas Firefox returns '1/1/1970 9:30:00 am' for the same date (UNIX epoch)."
                + " Additionally timezone information may be revealed."
                + " For instance the above were taken on a computer configured for CST (+9:30), which is why the times shown aren't midnight.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(conn, sampleCount, "MathTan", fingerprint.getMathTan());
        bean.setName("Math/Tan function");
        bean.setNameHoverText(
            "The same math functions run on different platforms and browsers can produce different results."
                + " In particular we are interested in the output of Math.tan(-1e300), which has been observed to produce different values depending on operating system."
                + " For instance on a 64bit Linux machine it produces the value -1.4214488238747245 and on a Windows machine it produces the value -4.987183803371025.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(conn, sampleCount, "UsingTor", fingerprint.isUsingTor());
        bean.setName("Using Tor?");
        bean.setNameHoverText(
            "Checks whether a client's request came from a Tor exit node, and hence whether they're using Tor."
                + " It does so by performing a TorDNSEL request for each client.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(conn, sampleCount, "AdsBlocked", fingerprint.getAdsBlocked());
        bean.setName("Ads blocked?");
        bean.setNameHoverText(
            "Checks whether ad blocking software is installed."
                + " It does so by attempting to display an ad and checking whether it was successful.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(conn, sampleCount, "Canvas", fingerprint.getCanvas());
        bean.setName("Canvas");
        if (bean.getValue().equals(NO_JAVASCRIPT) == false
            && bean.getValue().equals(NOT_SUPPORTED) == false) {
          bean.setValue("<img width=\"400\" height=\"60\" src=\"" + bean.getValue() + "\">");
        }
        bean.setNameHoverText(
            "Rendering of a specific picture with the HTML5 Canvas element following a fixed set of instructions."
                + " The picture presents some slight noticeable variations depending on the OS and the browser used.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(conn, sampleCount, "WebGLVendor", fingerprint.getWebGLVendor());
        bean.setName("WebGL Vendor");
        bean.setNameHoverText(
            "Name of the WebGL Vendor. Some browsers give the full name of the underlying graphics card used by the device.");
        characteristics.add(bean);
      }
      {
        CharacteristicBean bean =
            getCharacteristicBean(
                conn, sampleCount, "WebGLRenderer", fingerprint.getWebGLRenderer());
        bean.setName("WebGL Renderer");
        bean.setNameHoverText(
            "Name of the WebGL Renderer. Some browsers give the full name of the underlying graphics driver.");
        characteristics.add(bean);
      }

      return sampleID;

    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      // Close the connection
      // Finally triggers even if we return
      if (conn != null) {
        try {
          conn.close();
        } catch (SQLException e) {
          // Ignore
        }
      }
    }
    return null;
  }