public class OperationalParams implements OperationalParamsIntf {
    public static String AAID = "EBA0#0001";
    public static byte[] defaultAttestCert = android.util.Base64.decode("MIIB9zCCAZ+gAwIBAgIEV0ao6DAJBgcqhkjOPQQBMIGEMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExETAPBgNVBAcMCFNhbiBKb3NlMRMwEQYDVQQKDAplQmF5LCBJbmMuMQwwCgYDVQQLDANUTlMxEjAQBgNVBAMMCWVCYXksIEluYzEeMBwGCSqGSIb3DQEJARYPbnBlc2ljQGViYXkuY29tMB4XDTE2MDUyNjA3NDIzM1oXDTE2MDYwNTA3NDIzM1owgYQxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTERMA8GA1UEBwwIU2FuIEpvc2UxEzARBgNVBAoMCmVCYXksIEluYy4xDDAKBgNVBAsMA1ROUzESMBAGA1UEAwwJZUJheSwgSW5jMR4wHAYJKoZIhvcNAQkBFg9ucGVzaWNAZWJheS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARt45XjwaE0nV8B8qqhQGlXG9A/HoyBYUvF2WUAPGdH/Jc+B/yFCyEOV+HwbOVh6LpgMOwHCAhe68oEkva7Mm22MAkGByqGSM49BAEDRwAwRAIgbsqkzOb9dA2t3Y2NNLiWzmIeCHRm/e5H+KJsT1D8z+MCIAjBSjxq5kRAo/WQuN0+FObzXlParCwxJd2xWP1l3rxM", android.util.Base64.DEFAULT);
    public static byte[] defaultAttestPrivKey = android.util.Base64.decode("MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgy51Vu4OJrB9lceKjxyEtxqQkp4RfbN0WwfUlNdRMSjegCgYIKoZIzj0DAQehRANCAARt45XjwaE0nV8B8qqhQGlXG9A_HoyBYUvF2WUAPGdH_Jc-B_yFCyEOV-HwbOVh6LpgMOwHCAhe68oEkva7Mm22", android.util.Base64.URL_SAFE);
    public static byte[] defaultAttestPubKey = android.util.Base64.decode("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbeOV48GhNJ1fAfKqoUBpVxvQPx6MgWFLxdllADxnR_yXPgf8hQshDlfh8GzlYei6YDDsBwgIXuvKBJL2uzJttg", android.util.Base64.URL_SAFE);
    private Logger logger = Logger.getLogger(this.getClass().getName());
    private String aaid;
    private byte[] attestCert;
    private byte[] attestPrivKey;
    private StorageInterface storage;
    private String appIdPrefix = "appId::";
    private Authenticator authenticator;

    @Override
    public Authenticator getAuthenticator() {
        return this.authenticator;
    }

    @Override
    public String getAAID() {
        return aaid;
    }

    @Override
    public byte[] getAttestCert() {
        return attestCert;
    }

    @Override
    public long getRegCounter() {
        return Preferences.getSettingsParamLong("regCounter");
    }

    @Override
    public void incrementRegCounter() {
        long regCount = Preferences.getSettingsParamLong("regCounter");
        Preferences.setSettingsParamLong("regCounter", regCount);
    }

    @Override
    public long getAuthCounter() {
        return 0;
    }

    @Override
    public void incrementAuthCounter() {

    }

    @Override
    public boolean isFacetIdValid(String s, String s1) {
        return true;
    }

    @Override
    @TargetApi(Build.VERSION_CODES.M)
    public byte[] signWithAttestationKey(byte[] signedDataValue) throws Exception {
        KeyFactory kf = KeyFactory.getInstance("EC");
        byte[] signature = null;
        try {
            KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
            ks.load(null);
            PrivateKey privateKey = (PrivateKey) ks.getKey(ApplicationContextProvider.ATTEST_KEY, null);
//            PrivateKey privateKey =
//                    kf.generatePrivate(new PKCS8EncodedKeySpec(attestPrivKey));
            java.security.Signature s = java.security.Signature.getInstance("SHA256withECDSA");
            s.initSign(privateKey);
            s.update(SHA.sha(signedDataValue, "SHA-256"));
            signature = s.sign();
        } catch (KeyPermanentlyInvalidatedException invalidatedKeyException) {
            logger.info("invalidatedKeyException=" + invalidatedKeyException);
            //Can happen when user removes the screen lock
            throw new Exception("KeyInvalidatedByAndroidKeyStore");
        } catch (Exception e) {
            logger.info("e=" + e);
            throw new Exception("SystemError");
        }
        return signature;
    }

    @Override
    public StorageInterface getStorage() {
        return storage;
    }

    @Override
    @TargetApi(Build.VERSION_CODES.M)
    public KeyPairGenerator getKeyPairGenerator(String keyId) {
        try {
//            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
//            KeyStore.Entry entry = keyStore.getEntry(keyId, null);
//            if (entry != null) {
//                //TODO Should I return an error to Android RP app? Hey! You have to dereg first.
//                Log.i("createKeyPair", "There is already a keypair for this RP endpoint. Ok it will be overwritten.");
//            }
//
//            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
//            ECGenParameterSpec ecGenSpec = new ECGenParameterSpec("secp256r1");
//            keyPairGenerator.initialize(
//                    new KeyGenParameterSpec.Builder(keyName,
//                            KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
//                            .setDigests(KeyProperties.DIGEST_SHA256,
//                                    KeyProperties.DIGEST_SHA384,
//                                    KeyProperties.DIGEST_SHA512)
//                            .setAlgorithmParameterSpec(ecGenSpec)
//                            .setUserAuthenticationRequired(true)
//                            .setUserAuthenticationValidityDurationSeconds(60)
//                            .build());


            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
                    KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
            keyPairGenerator.initialize(
                    new KeyGenParameterSpec.Builder(
                            keyId,
                            KeyProperties.PURPOSE_SIGN)
                            .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
                            .setDigests(KeyProperties.DIGEST_SHA256,
                                    KeyProperties.DIGEST_SHA384,
                                    KeyProperties.DIGEST_SHA512)
                            // Only permit the private key to be used if the user authenticated
                            // within the last five minutes.
                            .setUserAuthenticationRequired(true)
                            .setUserAuthenticationValidityDurationSeconds(5 * 60)
                            .build());
            return keyPairGenerator;
        } catch (Exception e) {
            logger.info("getKeyPairGenerator: e=" + e);
            return null;
        }
    }

    @Override
    public RegRecord genAndRecord(String appId) {

        String keyId = genKeyId();
        keyId = keyId.substring(0, keyId.length() - 2);//removing =\n
        String previousKeyId = Preferences.getSettingsParam(appIdPrefix + appId);
        // Removing previous key material for the same appId (today, appId = RP Server)
        if (!previousKeyId.isEmpty()) {
            try {
                KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
                ks.load(null);
                ks.deleteEntry(previousKeyId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        Preferences.setSettingsParam(appIdPrefix + appId, keyId);
        RegRecord record = new RegRecord(
                keyId,
                getKeyPairGenerator(keyId).generateKeyPair().getPublic().getEncoded()
        );
//        storage.addRecord(record);
        return record;
    }

    private String genKeyId() {
        String tmp = getFacetId() + System.currentTimeMillis();
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA256");
            messageDigest.update(tmp.getBytes());
            return Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public String getFacetId(String appId) {
        /**
         * Skipping the check if the facet Id belong to a list of app's trusted.
         * This will save one service call.
         * It leaves the check to be done on the server.
         */
        return getFacetId();
    }

    public String getFacetId() {
        StringBuffer ret = new StringBuffer();
        String comma = "";
        try {
            Context context = ApplicationContextProvider.getContext();
            String callingPackageName = Preferences.getSettingsParam("callingPackageName");
            if (!callingPackageName.isEmpty()) {
                PackageInfo packageInfo = context.getPackageManager().getPackageInfo(callingPackageName, PackageManager.GET_SIGNATURES);
                for (Signature sign : packageInfo.signatures) {
                    MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
                    messageDigest.update(sign.toByteArray());
                    String currentSignature = Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT);
                    ret.append("android:apk-key-hash:");
                    ret.append(currentSignature.substring(0, currentSignature.length() - 2));
                    ret.append(comma);
                    comma = ",";
                }
            }
            Log.d("dummyclient","getFacetId: " +  ret.toString());
            return ret.toString();
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public String getKeyId(String appId) {
        return Preferences.getSettingsParam(appIdPrefix + appId).trim();
    }

    @Override
    public void init(String aaid, byte[] attestCert, byte[] attestPrivKey, StorageInterface storage) {
        this.aaid = aaid;
        this.attestCert = attestCert;
        this.attestPrivKey = attestPrivKey;
        this.storage = storage;
        this.authenticator = new Authenticator();
        this.fillAuthenticatorDetails();
        this.authenticator.aaid = aaid;
    }

    @Override
    @TargetApi(Build.VERSION_CODES.M)
    public byte[] getSignature(byte[] signedDataValue, String keyId) throws Exception {
        KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
        ks.load(null);
        PrivateKey privateKey = (PrivateKey) ks.getKey(keyId, null);
        byte[] signature = null;
        try {
            java.security.Signature s = java.security.Signature.getInstance("SHA256withECDSA");
            s.initSign(privateKey);
            s.update(SHA.sha(signedDataValue, "SHA-256"));
            signature = s.sign();

            /********* Test **************/
//            Log.i("TEST","Auth signature obtained...");
//            java.security.Signature ss = java.security.Signature.getInstance("SHA256withECDSA");
//            KeyFactory kf = KeyFactory.getInstance("EC");
//
//            KeyFactory factory = KeyFactory.getInstance(privateKey.getAlgorithm(), "AndroidKeyStore");
//            KeyInfo keyInfo = (KeyInfo) factory.getKeySpec(privateKey, KeyInfo.class);
//            Certificate certificate = ks.getCertificate(keyId);
//            PublicKey publicKey = certificate.getPublicKey();
//            String pkString = android.util.Base64.encodeToString(publicKey.getEncoded(), android.util.Base64.DEFAULT);
//
//            Log.i("TEST", "PublicKey: " + pkString);
//            ss.initVerify(kf.generatePublic(new X509EncodedKeySpec(publicKey.getEncoded())));
//            ss.update(SHA.sha(signedDataValue, "SHA-256"));
//            if(!ss.verify(signature)){
//                Log.i("TEST","verify failed.");
//            }
//            Log.i("TEST","Auth signature verified...");
            /********* Test **************/

        } catch (KeyPermanentlyInvalidatedException invalidatedKeyException) {
            //Can happen when user removes the screen lock
            throw new Exception("KeyInvalidatedByAndroidKeyStore");
        } catch (Exception e) {
            throw new Exception("SystemError: " + e.toString());
        }
        return signature;
    }

    public boolean removeKey(String appId) {
        String keyId = this.getKeyId(appId);
        try {
            KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
            ks.load(null);
            ks.deleteEntry(keyId);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    private void fillAuthenticatorDetails() {
        Version version = new Version(1, 0);

        authenticator.title = "Dummy UAF Authenticator";
        authenticator.aaid = "DMY0#0001";
        authenticator.description = "A dummy UAF Client suitable to conduct development tests on smartphones that are not FIDO Ready.";
        authenticator.supportedUAFVersions = new Version[1];
        authenticator.supportedUAFVersions[0] = version;
        authenticator.assertionScheme = "UAFV1TLV";
        authenticator.authenticationAlgorithm = (short) AlgAndEncodingEnum.UAF_ALG_SIGN_SECP256R1_ECDSA_SHA256_RAW.id;
        authenticator.attestationTypes = new short[1];
        authenticator.attestationTypes[0] = (short) TagsEnum.TAG_ATTESTATION_BASIC_FULL.id;
        authenticator.userVerification = UserVerifyEnum.USER_VERIFY_FINGERPRINT.getValue();
        authenticator.keyProtection = (short) (KeyProtectionEnum.KEY_PROTECTION_HARDWARE.getValue() | KeyProtectionEnum.KEY_PROTECTION_TEE.getValue());
        authenticator.matcherProtection = MatcherProtectionEnum.MATCHER_PROTECTION_TEE.getValue();
        authenticator.attachmentHint = AttachmentHintEnum.ATTACHMENT_HINT_INTERNAL.getValue();
        authenticator.isSecondFactorOnly = false;
        authenticator.tcDisplay = TCDEnum.TRANSACTION_CONFIRMATION_DISPLAY_ANY.getValue();
        authenticator.tcDisplayContentType = "text/plain";
        authenticator.tcDisplayPNGCharacteristics = null;
        authenticator.icon = "https://github.com/emersonmello/dummyuafclient/blob/master/app/src/main/res/mipmap-hdpi/ic_launcher.png";
        authenticator.supportedExtensionIDs = null;
    }

} public class Preferences {
	
	private static String PREFERENCES = "Preferences";

	public static String getSettingsParam(String paramName) {
		SharedPreferences settings = getPrefferences();
		return settings.getString(paramName, "");
	}

	public static long getSettingsParamLong(String paramName) {
		SharedPreferences settings = getPrefferences();
		return settings.getLong(paramName, 0);
	}

	public static SharedPreferences getPrefferences() {
		SharedPreferences settings = ApplicationContextProvider.getContext()
				.getSharedPreferences(PREFERENCES, 0);
		return settings;
	}

	public static void setSettingsParam(String paramName, String paramValue) {
		SharedPreferences settings = getPrefferences();
		SharedPreferences.Editor editor = settings.edit();
		editor.putString(paramName, paramValue);
		editor.apply();
	}

	public static void removeSettingsParam(String paramName) {
		SharedPreferences settings = getPrefferences();
		SharedPreferences.Editor editor = settings.edit();
		editor.remove(paramName);
		editor.apply();
	}

	public static void setSettingsParamLong(String paramName, long paramValue) {
		SharedPreferences settings = getPrefferences();
		SharedPreferences.Editor editor = settings.edit();
		editor.putLong(paramName, paramValue);
		editor.apply();
	}

	public static void setSettingsParamBoolean(String paramName, boolean paramValue){
		SharedPreferences settings = getPrefferences();
		SharedPreferences.Editor editor = settings.edit();
		editor.putBoolean(paramName, paramValue);
		editor.apply();
	}

	public static boolean getSettingsParamBoolean(String paramName){
		SharedPreferences settings = getPrefferences();
		return settings.getBoolean(paramName, true);
	}

} public class Storage implements StorageInterface {
    Gson gson = new GsonBuilder().create();
    String prefix = "storage::";

    @Override
    public void addRecord(RegRecord regRecord) {
        Preferences.setSettingsParam(prefix + regRecord.getKeyId(), gson.toJson(regRecord));
    }

    @Override
    public RegRecord get(String keyId) {
        return gson.fromJson(Preferences.getSettingsParam(prefix + keyId), RegRecord.class);
    }

    @Override
    public void remove(String keyId) {
        Preferences.removeSettingsParam(prefix + keyId);
    }
} public class InitConfig {

    private static InitConfig instance = new InitConfig();

    private boolean initialized = false;

    private OperationalParamsIntf operationalParams;

    public OperationalParamsIntf getOperationalParams() {
        return operationalParams;
    }

    private InitConfig() {

    }

    public static InitConfig getInstance() {
        return instance;
    }

    public boolean isInitialized() {
        return initialized;
    }

    public void init(String aaid, byte[] attestCert, byte[] attestPrivKey,
                     OperationalParamsIntf operationalParams, StorageInterface storage) {

        this.operationalParams = operationalParams;

        byte[] certificate = ApplicationContextProvider.generateAttestationKey(false);

        if (!(new String(certificate)).isEmpty()) {
            attestCert = certificate;
        }

        operationalParams.init(aaid, attestCert, attestPrivKey, storage);
        initialized = true;
    }

} public class Version {
    public int major;
    public int minor;
    
    public Version (int major, int minor){
		this.major = major;
		this.minor = minor;
    }
} public enum UAFIntentType {
		DISCOVER,
		DISCOVER_RESULT,
		CHECK_POLICY,
		CHECK_POLICY_RESULT,
		UAF_OPERATION,
		UAF_OPERATION_RESULT,
		UAF_OPERATION_COMPLETION_STATUS
} public class Auth {
	
	private Logger logger = Logger.getLogger(this.getClass().getName());
	private Gson gson = new GsonBuilder().disableHtmlEscaping().create(); 
	
	public String auth (String uafMsg, String appFacetId) throws UafMsgProcessException, UafResponseMsgParseException, UafRequestMsgParseException{
		logger.info ("  [UAF][1]Auth  ");
		AuthenticationRequestProcessor p = new AuthenticationRequestProcessor();
		AuthenticationResponse[] ret = new AuthenticationResponse[1];
		ret[0] = process(getAuthRequest(uafMsg), p, appFacetId);
		return getUafProtocolMsg( ret );
	}

	private AuthenticationResponse process(AuthenticationRequest uafMsg,
			AuthenticationRequestProcessor p, String appFacetId)
			throws UafMsgProcessException {
		try{
			AuthenticationResponse regResponse = p.processRequest(uafMsg, InitConfig.getInstance().getOperationalParams(), appFacetId);
			checkResult(regResponse);
			logger.info ("  [UAF][3]Auth - Auth Response Formed  ");
			logger.info(regResponse.assertions[0].assertion);
			logger.info ("  [UAF][4]Auth - done  ");
			return regResponse;
		} catch (Exception e){
			throw new UafMsgProcessException (e);
		}
	}

	private void checkResult(AuthenticationResponse regResponse) throws Exception {
		if (regResponse == null || regResponse.assertions == null || regResponse.assertions.length == 0 || regResponse.assertions[0] == null){
			throw new Exception ("Processing didn't return result");
		}
	}
	
	public AuthenticationRequest getAuthRequest(String uafMsg) throws UafRequestMsgParseException {
		try {
			logger.info ("  [UAF][2]Reg - getAuthRequest  : " + uafMsg);
			return gson.fromJson(uafMsg, AuthenticationRequest[].class)[0];
		} catch (Exception e){
			throw new UafRequestMsgParseException (e);
		}
	}

	public String getUafProtocolMsg (AuthenticationResponse[] ret) throws UafResponseMsgParseException{
		try{
			return getUafProtocolMsg(gson.toJson(ret));
		}catch (Exception e){
			throw new UafResponseMsgParseException(e);
		}
	}
	public String getUafProtocolMsg (String uafMsg){
		String msg = "{\"uafProtocolMessage\":";
		msg = msg + "\"";
		msg = msg + uafMsg.replace("\"","\\\"");
		msg = msg + "\"";
		msg = msg + "}";
		return msg;
	}
} public class Dereg {
    private Logger logger = Logger.getLogger(this.getClass().getName());
    private Gson gson = new GsonBuilder().create();

    public String dereg(String uafMsg, String appFacetId) throws UafRequestMsgParseException {
        logger.info("  [UAF][1]Dereg  ");
        DeregistrationRequest dereg = getDeregRequest(uafMsg);
        //InitConfig.getInstance().getOperationalParams().getStorage().remove(getKeyId(dereg));

        // Removing key from keyStore
        OperationalParams operationalParams = new OperationalParams();
        operationalParams.removeKey(appFacetId);

        // Removing details appId details from SharedPreferences
        SharedPreferences sharedPreferences = Preferences.getPrefferences();
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.remove("appId::" + appFacetId);
        editor.apply();

        return uafMsg;
    }

    private String getKeyId(DeregistrationRequest dereg) throws UafRequestMsgParseException {
        if (dereg == null || dereg.authenticators == null || dereg.authenticators.length == 0 || dereg.authenticators[0].keyID == null) {
            throw new UafRequestMsgParseException(new Exception("Invalid DeregistrationRequest: Missing KeyId"));
        }
        return dereg.authenticators[0].keyID;
    }

    public DeregistrationRequest getDeregRequest(String uafMsg) throws UafRequestMsgParseException {
        try {
            logger.info("  [UAF][2]Reg - getDeregRequest  : " + uafMsg);
            return gson.fromJson(uafMsg, DeregistrationRequest[].class)[0];
        } catch (Exception e) {
            throw new UafRequestMsgParseException(e);
        }
    }
} public class Reg {

    private Logger logger = Logger.getLogger(this.getClass().getName());
    private Gson gson = new Gson();

    public String register(String uafMsg, String appFacetId) throws UafMsgProcessException, UafRequestMsgParseException, UafResponseMsgParseException {
        logger.info("  [UAF][1]Reg  ");

        RegistrationResponse[] ret = new RegistrationResponse[1];
        RegistrationResponse regResponse = process(getRegistrationRequest(uafMsg), appFacetId);
        logger.info(regResponse.assertions[0].assertion);
        ret[0] = regResponse;
        return getUafProtocolMsg(ret);
    }

    public RegistrationResponse process(RegistrationRequest regRequest, String appFacetId)
            throws UafMsgProcessException {
        try {
            RegistrationRequestProcessor p = new RegistrationRequestProcessor();
            return p.processRequest(regRequest, InitConfig.getInstance()
                    .getOperationalParams(), appFacetId);
        } catch (Exception e) {
            throw new UafMsgProcessException(e);
        }
    }

    public RegistrationRequest getRegistrationRequest(String uafMsg) throws UafRequestMsgParseException {
        try {
            logger.info("  [UAF][3]Reg - getRegRequest  : " + uafMsg);
            return gson.fromJson(uafMsg, RegistrationRequest[].class)[0];
        } catch (Exception e) {
            throw new UafRequestMsgParseException(e);
        }
    }

    public String getUafProtocolMsg(RegistrationResponse[] ret)
            throws UafResponseMsgParseException {
        try {
            return getUafProtocolMsg(gson.toJson(ret));
        } catch (Exception e) {
            throw new UafResponseMsgParseException(e);
        }
    }

    public String getUafProtocolMsg(String uafMsg) {
        String msg = "{\"uafProtocolMessage\":";
        msg = msg + "\"";
        msg = msg + uafMsg.replace("\"", "\\\"");
        msg = msg + "\"";
        msg = msg + "}";
        return msg;
    }
} public class UafMsgProcessException extends Exception {

	public UafMsgProcessException(Exception e) {
		super(e);
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = 7494950106489420058L;

} public class UafRequestMsgParseException extends Exception {

	public UafRequestMsgParseException(Exception e) {
		super(e);
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = 2654274247574179420L;

} public class UafResponseMsgParseException extends Exception {

	public UafResponseMsgParseException(Exception e) {
		super(e);
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = -2517474242951105939L;

} public class Curl {

    private static final int CONNECTION_TIMEOUT = 20000;
    private static final int READ_TIMEOUT = 20000;
    private static final String CONTENT_TYPE_HEADER = "Content-Type";
    private static final String CONTENT_TYPE = "application/json";
    private static final String POST_METHOD = "POST";
    private static final String GET_METHOD = "GET";

    public static HttpResponse get(String url) {

        HttpURLConnection urlConnection = null;
        try {
            urlConnection = createConnection(url, GET_METHOD, false);

            int httpResult = urlConnection.getResponseCode();
            String response;
            if (httpResult == HttpURLConnection.HTTP_CREATED || httpResult == HttpURLConnection.HTTP_OK) {
                response = readStream(urlConnection.getInputStream());
            } else {
                response = readStream(urlConnection.getErrorStream());
            }
            return new HttpResponse(response, httpResult);
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
            Log.d("CURL", "Security error initialising HTTPS connection");
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("CURL", "Unable to connect to the server");
        } finally {
            if (urlConnection != null)
                urlConnection.disconnect();
        }
        return new HttpResponse("", 404);
    }

    protected static HttpResponse post(String url, String header, String payload) {

        HttpURLConnection urlConnection = null;
        try {
            urlConnection = createConnection(url, POST_METHOD, true);
            OutputStreamWriter out = new OutputStreamWriter(urlConnection.getOutputStream(), "utf-8");
            out.write(payload);
            out.close();

            int httpResult = urlConnection.getResponseCode();
            String response;
            if (httpResult == HttpURLConnection.HTTP_CREATED || httpResult == HttpURLConnection.HTTP_OK) {
                response = readStream(urlConnection.getInputStream());
            } else {
                response = readStream(urlConnection.getErrorStream());
            }

            return new HttpResponse(response, httpResult);

        } catch (GeneralSecurityException e) {
            e.printStackTrace();
            Log.d("CURL", "Security error initialising HTTPS connection");
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("CURL", "Unable to connect to the server");
        } finally {
            if (urlConnection != null)
                urlConnection.disconnect();
        }
        return new HttpResponse("", 404);
    }


    protected static HttpURLConnection createConnection(String fullUrl, String method, boolean output) throws
            IOException, KeyManagementException, NoSuchAlgorithmException {
        URL url = new URL(fullUrl);
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setDoOutput(output);
        urlConnection.setRequestMethod(method);
        urlConnection.setUseCaches(false);
        urlConnection.setConnectTimeout(CONNECTION_TIMEOUT);
        urlConnection.setReadTimeout(READ_TIMEOUT);
        urlConnection.setRequestProperty(CONTENT_TYPE_HEADER, CONTENT_TYPE);
        urlConnection.connect();
        return urlConnection;
    }

    protected static String readStream(InputStream stream) throws IOException {

        StringBuilder sb = new StringBuilder();
        BufferedReader br = new BufferedReader(new InputStreamReader(stream, "utf-8"));
        String line;
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }
        br.close();
        return sb.toString();
    }

    public static class HttpResponse {
        private final String payload;
        private final int httpStatusCode;

        public HttpResponse(String payload, int httpStatusCode) {
            this.httpStatusCode = httpStatusCode;
            this.payload = payload;
        }

        public String getPayload() {
            return payload;
        }

        public int getHttpStatusCode() {
            return httpStatusCode;
        }
    }
} public class DiscoveryData {
    public Version[] supportedUAFVersions;
    public String clientVendor;
    public Version clientVersion;
    public Authenticator[] availableAuthenticators;

    public static String getFakeDiscoveryData() {
        Version version = new Version(1, 0);
        DiscoveryData discoveryData = new DiscoveryData();

        discoveryData.clientVersion = version;
        discoveryData.supportedUAFVersions = new Version[1];
        discoveryData.supportedUAFVersions[0] = version;
        discoveryData.clientVendor = "DummyUAFClient Institute";
        discoveryData.availableAuthenticators = new Authenticator[1];

        if (!InitConfig.getInstance().isInitialized()) {
            try {
                InitConfig.getInstance()
                        .init(OperationalParams.AAID, OperationalParams.defaultAttestCert, OperationalParams.defaultAttestPrivKey, new OperationalParams(), new Storage());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        discoveryData.availableAuthenticators[0] = InitConfig.getInstance().getOperationalParams().getAuthenticator();

        String discoveryDataJson = (new Gson()).toJson(discoveryData);
        return discoveryDataJson;
    }
} public enum ErrorCode {
    NO_ERROR((short) 0x0, "NO ERROR"),
    WAIT_USER_ACTION((short) 0x1, "WAIT USER ACTION"),
    INSECURE_TRANSPORT((short) 0x2, "INSECURE TRANSPORT"),
    USER_CANCELLED((short) 0x3, "USER CANCELLED"),
    UNSUPPORTED_VERSION((short) 0x4, "UNSUPPORTED VERSION_1_0"),
    NO_SUITABLE_AUTHENTICATOR((short) 0x5, "NO SUITABLE AUTHENTICATOR"),
    PROTOCOL_ERROR((short) 0x6, "PROTOCOL ERROR"),
    UNTRUSTED_FACET_ID((short) 0x7, "UNTRUSTED FACET ID"),
    UNKNOWN((short) 0xFF, "UNKNOWN");

    private final short ID;
    private final String DESCRIPTION;

    ErrorCode(final short id, final String description) {
        this.ID = id;
        this.DESCRIPTION = description;
    }

    public short getID() {
        return this.ID;
    }

    public String getDESCRIPTION() {
        return this.DESCRIPTION;
    }

} public abstract class FidoUafUtils {

    public static String extractAppId(String serverResponse) {
        JSONArray requestArray = null;
        String appID = "";
        try {
            requestArray = new JSONArray(serverResponse);
            appID = ((JSONObject) requestArray.get(0)).getJSONObject("header").getString("appID");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return appID;
    }

    public static String updateAppId(String serverResponse, String facetIdList) {
        String appId = extractAppId(serverResponse);
        if (appId.isEmpty()) {
            try {
                JSONArray requestArray = new JSONArray(serverResponse);
                String facetId = (facetIdList.split(",").length > 1) ? facetIdList.split(",")[0] : facetIdList;
                ((JSONObject) requestArray.get(0)).getJSONObject("header").put("appID", facetId);
                return requestArray.toString();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return serverResponse;
    }


    /**
     * From among the objects in the trustedFacet array, select the one with the version matching
     * that of the protocol message version. The scheme of URLs in ids MUST identify either an
     * application identity (e.g. using the apk:, ios: or similar scheme) or an https: Web Origin [RFC6454].
     * Entries in ids using the https:// scheme MUST contain only scheme, host and port components,
     * with an optional trailing /. Any path, query string, username/password, or fragment information
     * MUST be discarded.
     *
     * @param trustedFacetsJson
     * @param version
     * @param appFacetId
     * @return true if appID list contains facetId (current Android application's signature).
     */
    public static boolean isFacetIdValid(String trustedFacetsJson, Version version, String appFacetId) {
        try {
            TrustedFacetsList trustedFacetsList = (new Gson()).fromJson(trustedFacetsJson, TrustedFacetsList.class);
            for (TrustedFacets trustedFacets : trustedFacetsList.getTrustedFacets()) {
                // select the one with the version matching that of the protocol message version
                if ((trustedFacets.getVersion().minor >= version.minor)
                        && (trustedFacets.getVersion().major <= version.major)) {
                    //The scheme of URLs in ids MUST identify either an application identity
                    // (e.g. using the apk:, ios: or similar scheme) or an https: Web Origin [RFC6454].
                    String[] searchHelper = appFacetId.split(",");
                    for (String facetId : searchHelper) {
                        for (String id : trustedFacets.getIds()) {
                            if (id.equals(facetId)) {
                                return true;
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    public static boolean isAppIdEqualsFacetId(String appFacetIds, String appID) {
        String[] searchHelper = appFacetIds.split(",");
        for (String facet : searchHelper) {
            if (facet.equals(appID)) {
                return true;
            }
        }
        return false;
    }
}
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.ebayopensource.fidouaf.marvin.OperationalParams;
import org.ebayopensource.fidouaf.marvin.Preferences;
import org.ebayopensource.fidouaf.marvin.Storage;
import org.ebayopensource.fidouaf.marvin.client.config.InitConfig;
import org.ebayopensource.fidouaf.marvin.client.msg.Version;
import org.ebayopensource.fidouaf.marvin.client.msg.client.UAFIntentType;
import org.ebayopensource.fidouaf.marvin.client.op.Auth;
import org.ebayopensource.fidouaf.marvin.client.op.Dereg;
import org.ebayopensource.fidouaf.marvin.client.op.Reg;
import org.ebayopensource.fidouaf.marvin.client.op.UafMsgProcessException;
import org.ebayopensource.fidouaf.marvin.client.op.UafRequestMsgParseException;
import org.ebayopensource.fidouaf.marvin.client.op.UafResponseMsgParseException;
import org.json.JSONObject;
import br.edu.ifsc.mello.dummyuafclient.fidouaflib.Curl;
import br.edu.ifsc.mello.dummyuafclient.fidouaflib.DiscoveryData;
import br.edu.ifsc.mello.dummyuafclient.fidouaflib.ErrorCode;
import br.edu.ifsc.mello.dummyuafclient.fidouaflib.FidoUafUtils;
import static br.edu.ifsc.mello.dummyuafclient.fidouaflib.ErrorCode.NO_ERROR;
                this.uafError(ErrorCode.UNKNOWN.getID(), null);
            }
        }
    }


    private void uafError(short errorCode, String uafintentType) {
        if (callingIntent != null) {
            Bundle bundle = new Bundle();
            String response = "";
            if (uafintentType != null) {
                bundle.putString("UAFIntentType", uafintentType);
            }
            bundle.putShort("errorCode", errorCode);
            bundle.putString("message", response);
            callingIntent.putExtras(bundle);
            setResult(Activity.RESULT_CANCELED, callingIntent);
        }
        mGetTrustedTask = null;
        finishAndRemoveTask();
    }

    @Override
    public void onError() {
        uafError(ErrorCode.UNKNOWN.getID(), null);
        showProgress(false);
    }

    @Override
    public void onAuthenticated() {
        this.processUAFIntentType(getIntent());
    }

    public void processUAFIntentType(Intent intent) {
        this.callingIntent = intent;

        String callingPackageName = getCallingPackage();
        Preferences.setSettingsParam("callingPackageName", callingPackageName);

        Bundle extras = intent.getExtras();
        if (extras != null) {
            String data = (String) extras.get("UAFIntentType");
            if (data != null) {
                if (data.equals(UAFIntentType.DISCOVER.name())) {
                    extras = new Bundle();
                    extras.putString("UAFIntentType", UAFIntentType.DISCOVER_RESULT.name());
                    extras.putShort("errorCode", NO_ERROR.getID());
                    extras.putString("discoveryData", DiscoveryData.getFakeDiscoveryData());
                    intent.putExtras(extras);
                    setResult(Activity.RESULT_OK, intent);
                    finishAndRemoveTask();
                }
                if (data.equals(UAFIntentType.UAF_OPERATION.name())) {
                    String message = (String) extras.get("message");
                    //TODO According to FIDO Protocol, it contains TLS information to be sent by the FIDO client to the FIDO Server
                    String channelBindings = (String) extras.get("channelBindings");
                    String inMsg = extract(message);

                    if (inMsg.isEmpty()) {
                        uafError(ErrorCode.PROTOCOL_ERROR.getID(), UAFIntentType.UAF_OPERATION_RESULT.name());
                        return;
                    }
                    OperationalParams operationalParams = new OperationalParams();
                    appFacetId = operationalParams.getFacetId();
                    if (inMsg.contains("Dereg")) {
                        try {
                            Dereg deregOp = new Dereg();
                            String response = deregOp.dereg(inMsg, appFacetId);
                            extras.putString("UAFIntentType", UAFIntentType.UAF_OPERATION_RESULT.name());
                            extras.putShort("errorCode", ErrorCode.NO_ERROR.getID());
                            extras.putString("message", response);
                            callingIntent.putExtras(extras);
                            setResult(Activity.RESULT_OK, callingIntent);
                            finishAndRemoveTask();
                        } catch (Exception e) {
                            Log.i("FIDOUAFClient", "processOp failed. e=" + e);
                            uafError(ErrorCode.UNKNOWN.getID(), null);
                        }
                    } else {
                        String appId = FidoUafUtils.extractAppId(inMsg);
                        if (appFacetId == null) {
                            Log.i("FIDOUAFClient", "processOp failed.");
                            uafError(ErrorCode.UNKNOWN.getID(), null);
                            return;
                        }
                        if (appId.isEmpty()) {
                            this.executeFIDOOperations(inMsg, appFacetId, false);
                        } else if (appId.contains(appFacetId)) {
                            inMsg = FidoUafUtils.updateAppId(inMsg, appFacetId);
                            this.executeFIDOOperations(inMsg, appFacetId, false);
                        } else {
                            showProgress(true);
                            mGetTrustedTask = new GetTrustedFacetsTask();
                            mGetTrustedTask.execute(appId, inMsg);
                        }
                    }
                }
            }
        } else {
            finishAndRemoveTask();
        }
    }


    private void executeFIDOOperations(String inMsg, String trustedFacets, boolean getAppId) {
        if (trustedFacets.isEmpty()) {
            uafError(ErrorCode.PROTOCOL_ERROR.getID(), UAFIntentType.UAF_OPERATION_RESULT.name());

            return;
        }

        Bundle extras = new Bundle();
        extras.putString("UAFIntentType", UAFIntentType.UAF_OPERATION_RESULT.name());

        String response = "";

        boolean allowUntrustedAppId = Preferences.getSettingsParamBoolean("allowuntrusted");

        if (!allowUntrustedAppId) {
            if (getAppId) {
                if (!FidoUafUtils.isFacetIdValid(trustedFacets, new Version(1, 0), appFacetId)) {