@Override
  public SecurityKeyData processSignResponse(SignResponse signResponse) throws U2FException {
    Log.info(">> processSignResponse");

    String sessionId = signResponse.getSessionId();
    String browserDataBase64 = signResponse.getClientData();
    String rawSignDataBase64 = signResponse.getSignatureData();

    SignSessionData sessionData = dataStore.getSignSessionData(sessionId);

    if (sessionData == null) {
      throw new U2FException("Unknown session_id");
    }

    String appId = sessionData.getAppId();
    SecurityKeyData securityKeyData = null;

    for (SecurityKeyData temp : dataStore.getSecurityKeyData(sessionData.getAccountName())) {
      if (Arrays.equals(sessionData.getPublicKey(), temp.getPublicKey())) {
        securityKeyData = temp;
        break;
      }
    }

    if (securityKeyData == null) {
      throw new U2FException("No security keys registered for this user");
    }

    String browserData = new String(Base64.decodeBase64(browserDataBase64));
    byte[] rawSignData = Base64.decodeBase64(rawSignDataBase64);

    Log.info("-- Input --");
    Log.info("  sessionId: " + sessionId);
    Log.info("  publicKey: " + Hex.encodeHexString(securityKeyData.getPublicKey()));
    Log.info("  challenge: " + Hex.encodeHexString(sessionData.getChallenge()));
    Log.info("  accountName: " + sessionData.getAccountName());
    Log.info("  browserData: " + browserData);
    Log.info("  rawSignData: " + Hex.encodeHexString(rawSignData));

    verifyBrowserData(
        new JsonParser().parse(browserData), "navigator.id.getAssertion", sessionData);

    AuthenticateResponse authenticateResponse =
        RawMessageCodec.decodeAuthenticateResponse(rawSignData);
    byte userPresence = authenticateResponse.getUserPresence();
    int counter = authenticateResponse.getCounter();
    byte[] signature = authenticateResponse.getSignature();

    Log.info("-- Parsed rawSignData --");
    Log.info("  userPresence: " + Integer.toHexString(userPresence & 0xFF));
    Log.info("  counter: " + counter);
    Log.info("  signature: " + Hex.encodeHexString(signature));

    if ((userPresence & UserPresenceVerifier.USER_PRESENT_FLAG) == 0) {
      throw new U2FException("User presence invalid during authentication");
    }

    if (counter <= securityKeyData.getCounter()) {
      throw new U2FException("Counter value smaller than expected!");
    }

    byte[] appIdSha256 = cryto.computeSha256(appId.getBytes());
    byte[] browserDataSha256 = cryto.computeSha256(browserData.getBytes());
    byte[] signedBytes =
        RawMessageCodec.encodeAuthenticateSignedBytes(
            appIdSha256, userPresence, counter, browserDataSha256);

    Log.info("Verifying signature of bytes " + Hex.encodeHexString(signedBytes));
    if (!cryto.verifySignature(
        cryto.decodePublicKey(securityKeyData.getPublicKey()), signedBytes, signature)) {
      throw new U2FException("Signature is invalid");
    }

    dataStore.updateSecurityKeyCounter(
        sessionData.getAccountName(), securityKeyData.getPublicKey(), counter);

    Log.info("<< processSignResponse");
    return securityKeyData;
  }