public interface MainActivityInterface {

    void makeAlertDialog(int titleID, String message);

    void makeAlertDialog(int titleID, int messageID);

    void makeAlertDialog(int titleID, String message, int positiveBtnTextID, boolean cancelable,
                         @Nullable DialogInterface.OnClickListener positiveBtnListener);

    void makeAlertDialog(int titleID, int messageID, int positiveBtnTextID, boolean cancelable,
                         @Nullable DialogInterface.OnClickListener positiveBtnListener);

    void makeDeviceNotSupportedDialog(Exception e);

    void makeToast(String message);

    void makeToast(int resID);

    void setStatusDialogText(String text);

    void setStatusDialogText(int id);

    void cancelStatusDialog();

    void getFirebaseTokenForPushRollout(Token token);

    void firebaseInit(FirebaseInitConfig firebaseInitConfig);

    void removeFirebase();

    void startTimer();

    void stopTimer();

    void resumeTimer();

    SecretKeyWrapper getWrapper();

    PublicKey generatePublicKeyFor(String alias);

    String getStringResource(int id);

    void cancelNotification(int notificationID);
} public interface PresenterInterface {
    /**
     * Initialization which is called directly after instantiation in OnCreate of the Activity.
     */
    void init();

    void scanQRfinished(ScanResult result);

    void addTokenFromIntent(String type, byte[] secret, String serial, int digits, String algorithm, String period, boolean withPIN);

    void cancelAuthentication(Token token);

    /**
     * Add a Push Authentication Request to the corresponding Token.
     * Finds the token by serial and includes a check for duplicates.
     *
     * @param request request that should be added
     */
    void addPushAuthRequest(PushAuthRequest request);

    /**
     * Passing the Lifecycle event, so the presenter can act on it.
     */
    void onResume();

    /**
     * Passing the Lifecycle event, so the presenter can act on it.
     */
    void onPause();

    /**
     * Passing the Lifecycle event, so the presenter can act on it.
     */
    void onStop();

    /**
     * Set the currently selected token to the given position in the list. Actions selected in ActionMode will be done with this token.
     *
     * @param position index position in the list
     */
    void setCurrentSelection(int position);

    void saveTokenlist();

    boolean isCurrentSelectionWithPin();

    boolean isCurrentSelectionPersistent();

    boolean isCurrentSelectionLocked();

    void removeCurrentSelection();

    void removeCurrentAuthRequest(Token token);

    String getCurrentSelectionLabel();

    String getCurrentSelectionOTP();

    Token getCurrentSelection();

    void setCurrentSelectionLabel(String label);

    void changeCurrentSelectionPIN(int pin);

    Token getTokenAtPosition(int position);

    int getTokenCount();

    void addTokenAt(int position, Token token);

    void addToken(Token token);

    Token removeTokenAtPosition(int position);

    void startPushAuthentication(Token token);

    void startPushRolloutForPosition(int position);

    void increaseHOTPCounter(Token token);

    boolean checkPIN(String input, Token token);

    void setPIN(String input, Token token);

    void timerProgress(int progress);

    void firebaseTokenReceived(String fbtoken, Token token);

    /**
     * Remove the Push Authentication Request, because it was finished by the Service while the App was running.
     * The Request is identified by the notificationID and signature.
     *
     * @param notificationID the ID of the notification
     * @param signature      the signature of the Request
     */
    void pushAuthFinishedFor(String serial, int notificationID, String signature, boolean success);

    /**
     * Indicates that a broadcast from the PushAuthService was received to change the state of a token to "authenticating" and updating the UI
     *
     * @param serial         serial of the token
     * @param notificationID notificationID of the notification
     * @param signature      signature of the PushAuthRequest
     */
    void pushAuthStartedFor(String serial, int notificationID, String signature);
} public class FirebaseInitConfig {
    private String appID;
    private String api_key;
    private String projNumber;
    private String projID;

    public FirebaseInitConfig(String projID, String appID, String api_key, String projNumber) {
        this.projID = projID;
        this.appID = appID;
        this.api_key = api_key;
        this.projNumber = projNumber;
    }

    public String getProjID() {
        return projID;
    }

    public void setProjID(String projID) {
        this.projID = projID;
    }

    public String getAppID() {
        return appID;
    }

    public void setAppID(String appID) {
        this.appID = appID;
    }

    public String getApiKey() {
        return api_key;
    }

    public void setApi_key(String api_key) {
        this.api_key = api_key;
    }

    public String getProjNumber() {
        return projNumber;
    }

    public void setProjNumber(String projNumber) {
        this.projNumber = projNumber;
    }
} public class PushAuthRequest {
    private String nonce, url, serial, question, title, signature;
    private boolean sslVerify;
    private Date expiration;
    private int notificationID;

    public PushAuthRequest(String nonce, String url, String serial, String question, String title, String signature, int notificationID, boolean sslVerify) {
        this.nonce = nonce;
        this.url = url;
        this.serial = serial;
        this.question = question;
        this.title = title;
        this.signature = signature;
        this.sslVerify = sslVerify;
        // Add expiration time which is +2 Minutes
        Calendar now = Calendar.getInstance();
        now.add(Calendar.MINUTE, 2);
        this.expiration = now.getTime();
        this.notificationID = notificationID;
    }

    public int getNotificationID() {
        return notificationID;
    }

    public Date getExpiration() {
        return expiration;
    }

    public String getNonce() {
        return nonce;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getSerial() {
        return serial;
    }

    public void setSerial(String serial) {
        this.serial = serial;
    }

    public String getQuestion() {
        return question;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getSignature() {
        return signature;
    }

    public boolean isSslVerify() {
        return sslVerify;
    }
} public class ScanResult {
    // BASE
   public String type, serial, label;

    // NORMAL TOKEN
    public String secret, algorithm = "sha1";              // Default
    public int digits = 6, period = 30, counter = 1;       // Defaults
    public boolean pin = false, persistent = false, taptoshow = false;

    // TWO STEP TOKEN (addition)
    public boolean do2Step = false;
    public int phonepartlength = 10;                       // in bytes
    public int iterations = 10000;
    public int output_size = 160;                          // in bits

    // PUSH TOKEN
    public String rollout_url, enrollment_credential;
    public int ttl = 10;                                   // 10 Minutes is the default
    public FirebaseInitConfig firebaseInitConfig;
    public int push_version = 1;
    public boolean sslverify = true;

    public ScanResult(String type, String serial) {
        this.type = type;
        this.serial = serial;
    }
} public class Token {

    private String currentOTP;
    private byte[] secret;
    private String label;
    private String type;
    private int digits;
    private int period;
    private String algorithm = "HmacSHA1"; //default is SHA1
    private int counter;
    private boolean withPIN = false;
    private boolean isLocked = false;
    private String pin = "";
    private boolean withTapToShow = false;
    private boolean tapped = false;
    private boolean persistent = false;
    private String serial;

    public String enrollment_credential;
    public Date rollout_expiration;
    public String rollout_url;
    public boolean sslVerify = true;
    public boolean lastAuthHadError = false;

    private ArrayList<PushAuthRequest> pendingAuths = new ArrayList<>();
    public State state = UNFINISHED;

    public Token(byte[] secret, String serial, String label, String type, int digits) {
        this.secret = secret;
        this.serial = serial;
        this.label = label;
        this.type = type;
        this.digits = digits;
        this.period = this.type.equals(TOTP) ? 30 : 0;
        this.counter = 0;
    }

    public ArrayList<PushAuthRequest> getPendingAuths() {
        return pendingAuths;
    }

    /**
     * Add the request if it is not yet present. Comparison is by notificationID and signature.
     *
     * @param request Request that should be added
     * @return true if successful, false if not (duplicate)
     */
    public boolean addPushAuthRequest(PushAuthRequest request) {
        for (PushAuthRequest req : pendingAuths) {
            if (req.getNotificationID() == request.getNotificationID()
                    && req.getSignature().equals(request.getSignature())) {
                return false;
            }
        }
        pendingAuths.add(request);
        return true;
    }

    public void setPendingAuths(ArrayList<PushAuthRequest> pendingAuths) {
        this.pendingAuths = pendingAuths;
    }

    // A push token only contains the serial and a label
    public Token(String serial, String label) {
        type = PUSH;
        this.serial = serial;
        this.label = label;
    }

    public String getSerial() {
        return serial;
    }

    public void setTapped(boolean tapped) {
        this.tapped = tapped;
    }

    public boolean isTapped() {
        return tapped;
    }

    public boolean isWithTapToShow() {
        return withTapToShow;
    }

    public void setWithTapToShow(boolean withTapToShow) {
        this.withTapToShow = withTapToShow;
    }

    public String getPin() {
        return pin;
    }

    public void setPin(String pin) {
        this.pin = pin;
    }

    public boolean isLocked() {
        return isLocked;
    }

    public void setLocked(boolean locked) {
        isLocked = locked;
    }

    public void setWithPIN(boolean withPIN) {
        this.isLocked = withPIN;
        this.withPIN = withPIN;
    }

    public boolean isWithPIN() {
        return withPIN;
    }

    public void setPeriod(int period) {
        this.period = period;
    }

    public int getCounter() {
        return counter;
    }

    public void setCounter(int counter) {
        this.counter = counter;
    }

    public void setSecret(byte[] secret) {
        this.secret = secret;
    }

    public byte[] getSecret() {
        return secret;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public String getLabel() {
        return label;
    }

    public String getType() {
        return type;
    }

    public void setAlgorithm(String algorithm) {
        // In the KeyURI the parameter is sha1/sha256/sha512, whereas the Mac instance is HmacSHA1 etc.
        if (algorithm.startsWith("sha") || algorithm.startsWith("SHA")) {
            this.algorithm = "Hmac" + algorithm.toUpperCase();
        } else if (algorithm.startsWith("Hmac")) {
            this.algorithm = algorithm;
        }
    }

    public int getDigits() {
        return digits;
    }

    public int getPeriod() {
        return period;
    }

    public String getAlgorithm() {
        return algorithm;
    }

    public String getCurrentOTP() {
        return currentOTP;
    }

    public void setCurrentOTP(String currentOTP) {
        this.currentOTP = currentOTP;
    }

    public boolean isPersistent() {
        return persistent;
    }

    public void setPersistent(boolean val) {
        persistent = val;
    }
} public class Presenter implements PresenterInterface, PresenterTaskInterface, PresenterUtilInterface, PushAuthCallbackInterface {

    private TokenListViewInterface tokenListInterface;
    private MainActivityInterface mainActivityInterface;
    private Model model;
    private Util util;

    private ArrayList<Pair<Token, PushAuthTask>> runningAuthentications = new ArrayList<>();
    private ArrayList<Pair<Token, PushAuthRequest>> toDelete = new ArrayList<>();

    public Presenter(TokenListViewInterface tokenListViewInterface, MainActivityInterface mainActivityInterface, Util util) {
        this.tokenListInterface = tokenListViewInterface;
        this.mainActivityInterface = mainActivityInterface;
        this.util = util;
    }

    @Override
    public void init() {
        // Logic of onCreate
        if (model == null) {
            try {
                model = new Model(util.loadTokens());
            } catch (IOException e) { // TODO handle exceptions
                e.printStackTrace();
                mainActivityInterface.makeDeviceNotSupportedDialog(e);
            } catch (GeneralSecurityException e) {
                e.printStackTrace();
                mainActivityInterface.makeDeviceNotSupportedDialog(e);
            }
        }
        if (model ==  null) {
            model = new Model();
        }
        FirebaseInitConfig firebaseInitConfig = null;
        try {
            firebaseInitConfig = util.loadFirebaseConfig();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (GeneralSecurityException e) {  // TODO handle e
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        if (firebaseInitConfig != null) {
            mainActivityInterface.firebaseInit(firebaseInitConfig);
        }

        mainActivityInterface.startTimer();
        refreshOTPs();

        String expired = model.checkForExpiredTokens();
        if (expired != null) {
            mainActivityInterface.makeAlertDialog(R.string.TokenExpiredTitle, expired.trim());
            tokenListInterface.notifyChange();
        }
        checkForExpiredAuths();
    }

    @Override
    public void scanQRfinished(ScanResult result) {
        Token token;
        switch (result.type) {
            case PUSH: {
                // Check the push push_version first
                // The current setup is v1
                if (result.push_version > 1) {
                    // Higher than v1 is currently not supported
                    mainActivityInterface.makeAlertDialog(R.string.IncompatiblePushVersion,
                            R.string.IncompatiblePushVersionMessage);
                    return;
                }

                token = new Token(result.serial, result.label);
                if (result.firebaseInitConfig != null) {
                    try {
                        mainActivityInterface.firebaseInit(result.firebaseInitConfig);
                        util.storeFirebaseConfig(result.firebaseInitConfig);
                    } catch (InvalidKeyException e) {
                        e.printStackTrace();
                    } catch (IllegalArgumentException e) {
                        // mainActivityInterface.firebaseInit() will throw this exception if it
                        // least one of the parameters is empty; we do not want to add a Token then,
                        // as it will be broken too
                        mainActivityInterface.makeAlertDialog(R.string.firebase_config_broken_title,
                                R.string.firebase_config_broken);
                        return;
                    } catch (GeneralSecurityException e) { // TODO handle e
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                token.sslVerify = result.sslverify;
                token.state = UNFINISHED;
                Calendar now = Calendar.getInstance();
                now.add(Calendar.MINUTE, result.ttl);
                token.rollout_expiration = now.getTime();
                token.rollout_url = result.rollout_url;
                token.enrollment_credential = result.enrollment_credential;
                addToken(token);
                preparePushRollout(token);
                break;
            }
            case HOTP:
            case TOTP: {
                if (result.secret == null) return;
                result.secret = result.secret.toUpperCase();
                token = new Token(new Base32().decode(result.secret), result.serial, result.label, result.type, result.digits);
                token.setCounter(result.counter);
                token.setPeriod(result.period);
                token.setAlgorithm(result.algorithm);
                token.setWithPIN(result.pin);
                token.setPersistent(result.persistent);
                token.setWithTapToShow(result.taptoshow);
                if (result.do2Step) {
                    doTwoStepRollout(token, result.phonepartlength, result.iterations, result.output_size);
                } else {
                    rolloutFinished(token);
                }
                break;
            }
            default:
                break;
        }
    }

    public void setModel(Model model) {
        this.model = model;
    }

    @Override
    public void addTokenFromIntent(String type, byte[] secret, String serial, int digits, String algorithm, String period, boolean withPIN) {
        // Push tokens cannot be created manually so this is simplified
        // serial = label on creation, the label can be edited afterwards
        Token token = new Token(secret, serial, serial, type, digits);

        if (type.equals(TOTP)) {
            token.setPeriod(Integer.parseInt(period));
        }
        if (algorithm != null) {
            token.setAlgorithm(algorithm);
        }
        if (withPIN) {
            token.setWithPIN(true);
        }
        rolloutFinished(token);
        mainActivityInterface.makeToast(mainActivityInterface.getStringResource(R.string.TokenAddedToast) + serial);
    }

    @Override
    public void cancelAuthentication(Token token) {
        // Reset the token's state and cancel the task
        token.state = FINISHED;
        deleteRunningAuthenticationFor(token);
        tokenListInterface.notifyChange();
    }

    @Override
    public void addPushAuthRequest(PushAuthRequest request) {
        // Requests for token that are not enrolled yet are not allowed
        Token token = model.getTokenBySerial(request.getSerial());
        if (token == null) return;
        if (!token.state.equals(UNFINISHED)) {
            logprint("Push Auth Request for " + request.getSerial() + " added.");
            if (token.addPushAuthRequest(request))
                tokenListInterface.notifyChange();
        }
    }

    @Override
    public void onResume() {
        refreshOTPs();
        mainActivityInterface.resumeTimer();
    }

    @Override
    public void onPause() {
        mainActivityInterface.stopTimer();
    }

    @Override
    public void onStop() {
        saveTokenlist();
    }

    @Override
    public void saveTokenlist() {
        try {
            util.saveTokens(model.getTokens());
        } catch (GeneralSecurityException e) {
            e.printStackTrace(); // TODO handle e
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void checkKeyStoreIsWorking() {
        try {
            util.saveToFile("test", new byte[]{});
        } catch (GeneralSecurityException | IOException e) {
            mainActivityInterface.makeDeviceNotSupportedDialog(e);
        }
    }

    @Override
    public void setCurrentSelection(int position) {
        model.setCurrentSelection(position);
    }

    @Override
    public boolean isCurrentSelectionWithPin() {
        if (model.getCurrentSelection() == null) return false;
        return model.getCurrentSelection().isWithPIN();
    }

    @Override
    public boolean isCurrentSelectionPersistent() {
        if (model.getCurrentSelection() == null) return false;
        return model.getCurrentSelection().isPersistent();
    }

    @Override
    public boolean isCurrentSelectionLocked() {
        if (model.getCurrentSelection() == null) return false;
        return model.getCurrentSelection().isLocked();
    }

    @Override
    public void removeCurrentSelection() {
        // callback from ActionMode delete
        if (model.getCurrentSelection() == null) return;
        this.removeToken(model.getCurrentSelection());
    }

    @Override
    public void removeCurrentAuthRequest(Token token) {
        PushAuthRequest req = token.getPendingAuths().remove(0);
        mainActivityInterface.cancelNotification(req.getNotificationID());
        tokenListInterface.notifyChange();
        token.lastAuthHadError = false;
    }

    @Override
    public String getCurrentSelectionLabel() {
        if (model.getCurrentSelection() == null) return null;
        return model.getCurrentSelection().getLabel();
    }

    @Override
    public String getCurrentSelectionOTP() {
        if (model.getCurrentSelection() == null) return null;
        return model.getCurrentSelection().getCurrentOTP();
    }

    @Override
    public Token getCurrentSelection() {
        return model.getCurrentSelection();
    }

    @Override
    public void setCurrentSelectionLabel(String label) {
        if (model.getCurrentSelection() == null) return;
        model.getCurrentSelection().setLabel(label);
    }

    @Override
    public void changeCurrentSelectionPIN(int pin) {
        if (model.getCurrentSelection() == null) return;
        String hashedPIN = hashPIN(pin, model.getCurrentSelection());
        model.getCurrentSelection().setPin(hashedPIN);
        tokenListInterface.notifyChange();
        saveTokenlist();
    }

    @Override
    public Token getTokenAtPosition(int position) {
        return model.getTokens().get(position);
    }

    @Override
    public int getTokenCount() {
        if (model.getTokens() == null) return 0;
        return model.getTokens().size();
    }

    @Override
    public void addTokenAt(int position, Token token) {
        model.getTokens().add(position, token);
    }

    @Override
    public void addToken(Token token) {
        if (!token.getType().equals(PUSH)) {
            if (token.getCurrentOTP() == null) {
                token.setCurrentOTP(generateOTP(token));
            }
        }
        model.getTokens().add(token);
    }

    @Override
    public Token removeTokenAtPosition(int position) {
        return model.getTokens().remove(position);
    }

    /**
     * Remove a token from the list. This includes public and private keys for Pushtoken
     *
     * @param currToken the token to remove
     */
    public void removeToken(Token currToken) {
        if (currToken == null) return;

        int position = model.getTokens().indexOf(currToken);
        tokenListInterface.removeProgressbar(position);

        if (model.getTokens().size() >= position && position >= 0 && !model.getTokens().isEmpty()) {
            model.getTokens().remove(position);
        }

        if (currToken.getType().equals(PUSH)) {
            util.removePubkeyFor(currToken.getSerial());
            getWrapper().removePrivateKeyFor(currToken.getSerial());
            // if the removed token was the last push token, remove the firebase config
            if (!model.hasPushToken()) {
                util.removeFirebaseConfig();
                mainActivityInterface.removeFirebase();
                mainActivityInterface.makeToast(R.string.ResetFirebaseInfoMsg);
            }
        }

        tokenListInterface.notifyChange();
        mainActivityInterface.makeToast(R.string.toast_token_removed);
        saveTokenlist();
    }

    @Override
    public void startPushAuthentication(Token token) {
        // onclick from token in list
        if (!token.getType().equals(PUSH)) return;
        // Always the first pendingAuth
        PushAuthRequest req = token.getPendingAuths().get(0);
        if (req == null || !req.getSerial().equals(token.getSerial()))
            return;
        if (req.getExpiration().compareTo(new Date()) < 1) {
            // Expired
            token.getPendingAuths().remove(req);
            tokenListInterface.notifyChange();
            return;
        }
        try {
            PrivateKey appPrivateKey = getWrapper().getPrivateKeyFor(req.getSerial());
            PublicKey piPublicKey = util.getPIPubkey(req.getSerial());
            if (appPrivateKey != null && piPublicKey != null) {
                token.state = AUTHENTICATING;
                PushAuthTask task = new PushAuthTask(token, req, piPublicKey, appPrivateKey, this);
                runningAuthentications.add(new Pair<>(token, task));
                task.execute();
                tokenListInterface.notifyChange();
            }
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (UnrecoverableEntryException e) {
            e.printStackTrace();
        } catch (GeneralSecurityException e) {      // TODO handle e
            e.printStackTrace();
        }
    }

    @Override
    public void startPushRolloutForPosition(int position) {
        // This method is called from the Tokenlistadapter when the Rollout is retried,
        // the adapter has only the position of the row
        preparePushRollout(model.getTokens().get(position));
    }

    /**
     * Sets the token in RollingOut state and gets the Firebase Token.
     * After getting the FB Token the rollout is started by a callback.
     * This ensures the FB Token is present.
     *
     * @param token token to start the rollout for
     */
    private void preparePushRollout(Token token) {
        token.state = ROLLING_OUT;
        tokenListInterface.notifyChange();

        // Start getting the Firebase Token from InstanceID
        // When finished there will be call to firebaseTokenReceived to start the Rollout
        mainActivityInterface.getFirebaseTokenForPushRollout(token);
    }

    @Override
    public void firebaseTokenReceived(String fbtoken, Token token) {
        // Called when InstanceID in MainActivity finished getting the Token
        // Now the Rollout can be started
        new PushRolloutTask(token, fbtoken, this).execute();
    }

    @Override
    public void pushAuthFinishedFor(String serial, int notificationID, String signature, boolean success) {
        Token token = model.getTokenBySerial(serial);
        if (token == null) return;
        token.state = FINISHED;
        if (success) {
            for (PushAuthRequest req : token.getPendingAuths()) {
                if (req.getSignature().equals(signature) && req.getNotificationID() == notificationID) {
                    logprint("Removing PushAuthReq");
                    token.getPendingAuths().remove(req);
                }
            }
        } else {
            token.lastAuthHadError = true;
        }
        tokenListInterface.notifyChange();
    }

    @Override
    public void pushAuthStartedFor(String serial, int notificationID, String signature) {
        Token token = null;
        for (Token t : model.getTokens()) {
            if (t.getSerial().equals(serial)) {
                token = t;
            }
        }
        if (token == null) return;
        token.state = AUTHENTICATING;
        tokenListInterface.notifyChange();
    }

    @Override
    public void increaseHOTPCounter(Token token) {
        token.setCounter((token.getCounter() + 1));
        token.setCurrentOTP(generateOTP(token));
        tokenListInterface.notifyChange();
    }

    @Override
    public boolean checkPIN(String input, Token token) {
        return hashPIN(Integer.parseInt(input), token).equals(token.getPin());
    }

    @Override
    public void setPIN(String input, Token token) {
        token.setPin(hashPIN(Integer.parseInt(input), token));
        token.setLocked(false);
        tokenListInterface.notifyChange();
    }

    @Override
    public void timerProgress(int progress) {
        tokenListInterface.updateProgressbars(progress);
        // refresh OTP values only around the periods
        if (progress < 3 || progress > 27 && progress < 33 || progress > 57) {
            refreshOTPs();
        }
        // Check for expired pendingAuths every 5s
        if (progress % 5 == 0) {
            checkForExpiredAuths();
        }
    }

    private void checkForExpiredAuths() {
        for (Token token : model.getTokens()) {
            if (token.getType().equals(PUSH)) {
                for (PushAuthRequest req : token.getPendingAuths()) {
                    if (new Date().after(req.getExpiration())) {
                        // Expired
                        toDelete.add(new Pair<>(token, req));
                    }
                }
            }
        }
        for (Pair<Token, PushAuthRequest> pair : toDelete) {
            pair.first.getPendingAuths().remove(pair.second);
            mainActivityInterface.cancelNotification(pair.second.getNotificationID());
        }
        if (!toDelete.isEmpty()) {
            tokenListInterface.notifyChange();
            toDelete.clear();
        }
    }

    private void refreshOTPs() {
        if (model != null) {
            for (int i = 0; i < model.getTokens().size(); i++) {
                if (!model.getTokens().get(i).getType().equals(PUSH)) {
                    model.getTokens().get(i).setCurrentOTP(generateOTP(model.getTokens().get(i)));
                }
            }
            tokenListInterface.notifyChange();
        }
    }

    private void doTwoStepRollout(Token token, int phonepartlength, int iterations, int output_size) {
        new TwoStepRolloutTask(token, phonepartlength, iterations, output_size, this).execute();
    }

    private void rolloutFinished(Token token) {
        if (token == null) return;
        if (!token.getType().equals(PUSH)) {
            token.setCurrentOTP(generateOTP(token));
        } else {
            // Do not add token twice
            for (Token t : model.getTokens()) {
                if (t.getSerial().equals(token.getSerial()) && t.getType().equals(PUSH)) {
                    saveTokenlist();
                    tokenListInterface.notifyChange();
                    return;
                }
            }
        }
        model.getTokens().add(token);
        saveTokenlist();
        tokenListInterface.notifyChange();
    }

    @Override
    public PublicKey generatePublicKeyFor(String serial) {
        return mainActivityInterface.generatePublicKeyFor(serial);
    }

    /**
     * Store the received key with the serial as alias. If there is an error when storing the key,
     * the Token state will stay marked as unfinished and can be repeated.
     *
     * @param key   the received key as raw String
     * @param token token to save the key for
     */
    @Override
    public void receivePublicKey(String key, Token token) {
        try {
            try {
                util.storePIPubkey(key, token.getSerial());
            } catch (IOException e) {
                e.printStackTrace();            // TODO handle e
            }
        } catch (GeneralSecurityException e) {
            // this means the "key" field was empty or the DECODED data is not a key
            updateTaskStatus(PRO_STATUS_RESPONSE_NO_KEY, token);
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            updateTaskStatus(PRO_STATUS_BAD_BASE64, token);
        }
    }

    /**
     * Updates the UI according to the statusCode. This includes opening/closing dialogs and choosing the dialog type.
     *
     * @param statusCode statusCode
     */
    @Override
    public void updateTaskStatus(int statusCode, Token token) {
        switch (statusCode) {
            //----------- STANDARD ROLLOUT -----------
            case STATUS_STANDARD_ROLLOUT_DONE:
                rolloutFinished(token);
                break;
            //----------- INIT FIREBASE -----------
            case STATUS_INIT_FIREBASE:
                //mainActivityInterface.setStatusDialogText(R.string.InitFirebaseStatus);
                break;
            case STATUS_INIT_FIREBASE_DONE:
                mainActivityInterface.cancelStatusDialog();
                break;
            //----------- TWO STEP ROLLOUT -----------
            case STATUS_TWO_STEP_ROLLOUT:
                mainActivityInterface.setStatusDialogText(R.string.WaitWhileSecretIsGenerated);
                break;
            case STATUS_TWO_STEP_ROLLOUT_DONE:
                rolloutFinished(token);
                mainActivityInterface.cancelStatusDialog();
                break;
            //----------- PUSH ROLLOUT -----------
            case PRO_STATUS_STEP_1:
                //mainActivityInterface.setStatusDialogText(R.string.PushRolloutStep1Status);
                break;
            case PRO_STATUS_STEP_2:
                //mainActivityInterface.setStatusDialogText(R.string.PushRolloutStep2Status);
                break;
            case PRO_STATUS_STEP_3:
                //mainActivityInterface.setStatusDialogText(R.string.PushRolloutStep3Status);
                break;
            case PRO_STATUS_DONE:
                token.state = FINISHED;
                rolloutFinished(token);
                mainActivityInterface.cancelStatusDialog();
                break;
            //----------- PUSH ROLLOUT ERRORS -----------
            case PRO_STATUS_BAD_BASE64:
                token.state = UNFINISHED;
                rolloutFinished(token);
                mainActivityInterface.makeAlertDialog(R.string.Error, R.string.KeyFromServerWrongFormat);
                break;
            case PRO_STATUS_MALFORMED_JSON:
                token.state = UNFINISHED;
                rolloutFinished(token);
                mainActivityInterface.makeAlertDialog(R.string.Error, R.string.ResponseNotParsed);
                break;
            case PRO_STATUS_RESPONSE_NO_KEY:
                token.state = UNFINISHED;
                rolloutFinished(token);
                mainActivityInterface.makeAlertDialog((R.string.Error), (R.string.ResponseNoKey));
                break;
            case PRO_STATUS_REGISTRATION_TIME_EXPIRED:
                mainActivityInterface.makeAlertDialog(R.string.Error, R.string.RegistrationTimeExpired);
                removeToken(token);
                break;
            case STATUS_ENDPOINT_MALFORMED_URL:
                mainActivityInterface.makeAlertDialog(R.string.Error, R.string.RolloutURLinvalid);
                break;
            case STATUS_ENDPOINT_UNKNOWN_HOST:
                token.state = UNFINISHED;
                rolloutFinished(token);
                mainActivityInterface.makeAlertDialog(R.string.Error, R.string.RolloutURLNotResolved);
                break;
            case PRO_STATUS_RESPONSE_NOT_OK:
            case STATUS_ENDPOINT_ERROR:
                token.state = UNFINISHED;
                rolloutFinished(token);
                mainActivityInterface.makeAlertDialog(R.string.Error, R.string.ServerResponseNotOk);
                break;
            case STATUS_ENDPOINT_SSL_ERROR:
                token.state = UNFINISHED;
                rolloutFinished(token);
                mainActivityInterface.makeAlertDialog(R.string.Error, R.string.SSLHandshakeFailed);
                break;
            default:
                logprint("Unknown statusCode in updateTaskStatus: " + statusCode);
                break;
        }
        tokenListInterface.notifyChange();
    }

    @Override
    public void twoStepFinished(String messageToShow) {
        mainActivityInterface.makeAlertDialog(R.string.TwoStepFinishDialogTitle, messageToShow, R.string.ButtonOK,
                false, (dialog, which) -> dialog.dismiss());
    }

    @Override
    public SecretKeyWrapper getWrapper() {
        return mainActivityInterface.getWrapper();
    }

    @Override
    public void authenticationFinished(boolean success, Token token) {
        if (success) {
            mainActivityInterface.makeToast(R.string.AuthenticationSuccessful);
            // Remove the notification if still present
            if (!token.getPendingAuths().isEmpty()) {
                mainActivityInterface.cancelNotification(token.getPendingAuths().get(0).getNotificationID());
                // In case of success, remove the pendingAuth from the token (always the first from within the App)
                token.getPendingAuths().remove(0);
            }
            token.state = FINISHED;
            tokenListInterface.notifyChange();
        } else {
            mainActivityInterface.makeToast(R.string.AuthenticationFailed);
        }
        // Regardless of success, remove the token from runningAuthentications
        deleteRunningAuthenticationFor(token);
    }

    /**
     * Cancel the running Authentication Task and remove the pair from the runningAuthentications List
     * Sets the tokens 'lastAuthHadError' so the authentication can be dismissed
     *
     * @param token token of the pair
     */
    private void deleteRunningAuthenticationFor(Token token) {
        Pair<Token, PushAuthTask> toDelete = null;
        for (Pair<Token, PushAuthTask> pair :
                runningAuthentications) {
            if (pair.first.getSerial().equals(token.getSerial())) {
                toDelete = pair;
            }
        }
        if (toDelete != null) {
            toDelete.second.cancel(true);
            runningAuthentications.remove(toDelete);
        }
        token.lastAuthHadError = true;
        token.state = FINISHED;
    }

    @Override
    public void handleAuthError(int statusCode, Token token) {
        token.lastAuthHadError = true;
        switch (statusCode) {
            case STATUS_ENDPOINT_UNKNOWN_HOST: {
                // Network unreachable
                cancelAuthentication(token);
                mainActivityInterface.makeToast(R.string.ERR_SERVER_UNREACHABLE);
                break;
            }
            case STATUS_ENDPOINT_SSL_ERROR: {
                cancelAuthentication(token);
                mainActivityInterface.makeToast(R.string.SSLHandshakeFailed);
                break;
            }
            case PA_INVALID_SIGNATURE: {
                cancelAuthentication(token);
                token.getPendingAuths().remove(0);
                mainActivityInterface.makeToast(R.string.PushAuthInvalidSignature);
                break;
            }
            case PA_SIGNING_FAILURE: {
                cancelAuthentication(token);
                token.getPendingAuths().remove(0);
                mainActivityInterface.makeToast(R.string.PushAuthSigningError);
                break;
            }
            default:
                logprint("No handling for errorcode " + statusCode + " in presenter.");
                break;
        }
    }
} public class AppConstants {

    public static final String APP_TITLE = " privacyIDEA Authenticator";
    public static final String PACKAGE_NAME = "it.netknights.piauthenticator";
    public static final String TAG = "PI-Authenticator";

    public static final int INTENT_ADD_TOKEN_MANUALLY = 101;
    public static final int INTENT_ABOUT = 102;
    public static final int PERMISSIONS_REQUEST_CAMERA = 103;

    // ----- CRYPTO CONSTANTS -----
    public static final String CRYPT_ALGORITHM = "AES/GCM/NoPadding";
    public static final String KEY_WRAP_ALGORITHM = "RSA/ECB/PKCS1Padding";
    public static final int KEY_LENGTH = 16;
    public static final int IV_LENGTH = 12;
    public static final String SIGNING_ALGORITHM = "SHA256withRSA";

    // ----- FILE NAMES -----
    public static final String DATAFILE = "data.dat";
    public static final String KEYFILE = "key.key";
    public static final String PUBKEYFILE = "pubkey.key";
    public static final String FB_CONFIG_FILE = "fbconf.dat";
    // -----------------------

    public static final String DIGITS = "digits";
    public static final String PERIOD = "period";
    public static final String ALGORITHM = "algorithm";
    public static final String ISSUER = "issuer";
    public static final String SECRET = "secret";
    public static final String TYPE = "type";
    public static final String LABEL = "label";
    public static final String COUNTER = "counter";
    public static final String TOTP = "totp";
    public static final String HOTP = "hotp";
    public static final String TAPTOSHOW = "taptoshow";
    public static final String PIN = "pin";
    public static final String WITHPIN = "withpin";
    public static final String TWOSTEP_SALT = "2step_salt";                // Size of the random bytes generated by the smartphone, which are used as salt for PBKDF2
    public static final String TWOSTEP_DIFFICULTY = "2step_difficulty";    // PBKDF2 iterations
    public static final String TWOSTEP_OUTPUT = "2step_output";            // size of the key generated by PBKDF2
    public static final String PROPERTY_PROGRESS = "progress";
    public static final String PERSISTENT = "persistent";

    public static final String SHA1 = "SHA1";
    public static final String SHA256 = "SHA256";
    public static final String SHA512 = "SHA512";
    public static final String HMACSHA1 = "HmacSHA1";
    public static final String HMACSHA256 = "HmacSHA256";
    public static final String HMACSHA512 = "HmacSHA512";

    public static final String PERIOD_30_STR = "30s";
    public static final String PERIOD_60_STR = "60s";
    public static final int PERIOD_30 = 30;
    public static final int PERIOD_60 = 60;

    public static final String DIGITS_6_STR = "6";
    public static final String DIGITS_8_STR = "8";

    // ----- Stuff for push -----
    // Attribute names
    public static final String PUSH = "pipush";
    public static final String URL = "url";
    public static final String TTL = "ttl";
    public static final String PROJECT_ID = "projectid";
    public static final String APP_ID = "appid";
    public static final String API_KEY = "apikey";
    public static final String PROJECT_NUMBER = "projectnumber";
    public static final String FB_TOKEN = "fbtoken";
    public static final String ENROLLMENT_CRED = "enrollment_credential";

    // TODO
    public static final String TITLE = "title";
    public static final String NONCE = "nonce";
    public static final String SIGNATURE = "signature";
    public static final String SERIAL = "serial";
    public static final String QUESTION = "question";
    public static final String ROLLOUT_STATE = "rollout_state";
    public static final String ROLLOUT_EXPIRATION = "rollout_expiration";
    public static final String PUSH_VERSION = "v";
    public static final String SSL_VERIFY = "sslverify";
    public static final String PUBKEY = "pubkey";
    public static final String NOTIFICATION_ID = "notificationID";
    public static final String PENDING_AUTHS = "pending_auths";
    public static final String RESPONSE_DETAIL = "detail";
    public static final String RESPONSE_PUBLIC_KEY = "public_key";
    public static final String RESPONSE_RESULT = "result";
    public static final String RESPONSE_VALUE = "value";

    public enum State {
        UNFINISHED("unfinished"),
        ROLLING_OUT("rolling_out"),
        FINISHED("finished"),
        AUTHENTICATING("authenticating");

        String state;
        State(String state) {
            this.state = state;
        }
    }

    // Constants
    public static final int READ_TIMEOUT = 10000;
    public static final int CONNECT_TIMEOUT = 15000;

    public static final String CHANNEL_ID_HIGH_PRIO = "privacyIDEAPush_high";
    public static final String CHANNEL_ID_LOW_PRIO = "privacyIDEAPush_low";

    public static final String INTENT_FILTER = "privacyIDEAAuthenticator";

    // Status codes
    public static final int PRO_STATUS_STEP_1 = 1001;
    public static final int PRO_STATUS_STEP_2 = 1002;
    public static final int PRO_STATUS_STEP_3 = 1003;
    public static final int PRO_STATUS_DONE = 1004;
    public static final int PRO_STATUS_KEY_RECEIVED = 1005;
    public static final int PRO_STATUS_REGISTRATION_TIME_EXPIRED = 1010;
    public static final int PRO_STATUS_RESPONSE_NO_KEY = 1011;

    public static final int PRO_STATUS_BAD_BASE64 = 1013;
    public static final int PRO_STATUS_MALFORMED_JSON = 1015;
    public static final int PRO_STATUS_RESPONSE_NOT_OK = 1016;

    public static final int PA_INVALID_SIGNATURE = 2001;
    public static final int PA_SIGNING_FAILURE = 2002;
    public static final int PA_AUTHENTICATION_FINISHED = 2003;

    public static final int STATUS_INIT_FIREBASE = 3101;
    public static final int STATUS_INIT_FIREBASE_DONE = 3102;
    public static final int STATUS_TWO_STEP_ROLLOUT = 3103;
    public static final int STATUS_TWO_STEP_ROLLOUT_DONE = 3104;
    public static final int STATUS_STANDARD_ROLLOUT_DONE = 3105;

    public static final int STATUS_ENDPOINT_SENDING_COMPLETE = 4001;
    public static final int STATUS_ENDPOINT_UNKNOWN_HOST = 4002;
    public static final int STATUS_ENDPOINT_MALFORMED_URL = 4003;
    public static final int STATUS_ENDPOINT_RESPONSE_RECEIVED = 4004;
    public static final int STATUS_ENDPOINT_ERROR = 4005;
    public static final int STATUS_ENDPOINT_SSL_ERROR = 4006;
} public class SecretKeyWrapper {
    private final Cipher mCipher;
    private final KeyPair mPair;

    /**
     * Create a wrapper using the public/private key pair with the given alias.
     * If no pair with that alias exists, it will be generated.
     */
    @SuppressLint("GetInstance")
    public SecretKeyWrapper(Context context)
            throws GeneralSecurityException, IOException {
        mCipher = Cipher.getInstance(KEY_WRAP_ALGORITHM);

        final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        keyStore.load(null);

        if (!keyStore.containsAlias("settings")) {
            generateKeyPair("settings", context);
        }

        // Even if we just generated the key, always read it back to ensure we
        // can read it successfully.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            //Following code is for API 28+
            PrivateKey privateKey = (PrivateKey) keyStore.getKey("settings", null);
            Certificate certificate = keyStore.getCertificate("settings");
            KeyStore.PrivateKeyEntry entry = new KeyStore.PrivateKeyEntry(privateKey, new Certificate[]{certificate});
            mPair = new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey());
        } else {
            final KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) keyStore.getEntry("settings", null);
            mPair = new KeyPair(entry.getCertificate().getPublicKey(), entry.getPrivateKey());
        }

        /*final PrivateKey privateKey = (PrivateKey) keyStore.getKey("alias", null);
        final PublicKey publicKey = privateKey != null ? keyStore.getCertificate("alias").getPublicKey() : null;
        mPair = new KeyPair(publicKey, privateKey);*/
    }

    /**
     * Wrap a {@link SecretKey} using the public key assigned to this wrapper.
     * Use {@link #unwrap(byte[])} to later recover the original
     * {@link SecretKey}.
     *
     * @return a wrapped push_version of the given {@link SecretKey} that can be
     * safely stored on untrusted storage.
     */
    public byte[] wrap(SecretKey key) throws GeneralSecurityException {
        mCipher.init(Cipher.WRAP_MODE, mPair.getPublic());
        return mCipher.wrap(key);
    }

    /**
     * Unwrap a {@link SecretKey} using the private key assigned to this
     * wrapper.
     *
     * @param blob a wrapped {@link SecretKey} as previously returned by
     *             {@link #wrap(SecretKey)}.
     */
    public SecretKey unwrap(byte[] blob) throws GeneralSecurityException {
        mCipher.init(Cipher.UNWRAP_MODE, mPair.getPrivate());

        return (SecretKey) mCipher.unwrap(blob, "AES", Cipher.SECRET_KEY);
    }

    /**
     * Generate a KeyPair and store it with the given alias in the KeyStore.
     * Return the PublicKey
     *
     * @param alias   the alias to store the key with
     * @param context needed for KeyPairGeneratorSpec
     * @return the PublicKey of the just generated KeyPair
     */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public static PublicKey generateKeyPair(String alias, Context context) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException,
            NoSuchProviderException, InvalidAlgorithmParameterException, UnrecoverableEntryException {
        final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        keyStore.load(null);
        logprint("generateKeyPair for alias: " + alias);
        if (keyStore.containsAlias(alias)) {
            // TODO double entry_normal -> overwrite?
        }
        final Calendar start = new GregorianCalendar();
        final Calendar end = new GregorianCalendar();
        end.add(Calendar.YEAR, 100);
        final KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
        final KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
                .setAlias(alias)
                .setSubject(new X500Principal("CN=" + alias))
                .setSerialNumber(BigInteger.ONE)
                .setStartDate(start.getTime())
                .setEndDate(end.getTime())
                .setKeySize(4096)
                .build();
        gen.initialize(spec);
        gen.generateKeyPair();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            //Following code is for API 28+
            PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, null);
            Certificate certificate = keyStore.getCertificate(alias);
            return new KeyStore.PrivateKeyEntry(privateKey, new Certificate[]{certificate}).getCertificate().getPublicKey();
        } else {
            final KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null);
            return entry.getCertificate().getPublicKey();
        }
    }

    /**
     * Load the PrivateKey for the given alias/serial
     *
     * @param alias the alias to load the key for
     * @return the PrivateKey
     */
    public PrivateKey getPrivateKeyFor(String alias) throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, UnrecoverableEntryException {
        final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
        keyStore.load(null);
        if (!keyStore.containsAlias(alias)) {
            // TODO key not found
            return null;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            //Following code is for API 28+
            PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, null);
            Certificate certificate = keyStore.getCertificate(alias);
            return new KeyStore.PrivateKeyEntry(privateKey, new Certificate[]{certificate}).getPrivateKey();
        } else {
            final KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null);
            return entry.getPrivateKey();
        }
    }

    /**
     * Remove the privateKey from the Keystore for the given alias/serial
     *
     * @param alias the serial is the alias of the privateKey in the Keystore
     */
    public void removePrivateKeyFor(String alias) {
        try {
            final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);
            if (!keyStore.containsAlias(alias)) {
                logprint("key for alias " + alias + " was not found for deletion!");
                return;
            }
            keyStore.deleteEntry(alias);
            logprint("key for alias " + alias + " was deleted from keystore!");
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

} public class Util {

    private String baseFilePath;
    private SecretKeyWrapper secretKeyWrapper;
    private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public Util(SecretKeyWrapper secretKeyWrapper, String baseFilePath) {
        this.baseFilePath = baseFilePath;
        this.secretKeyWrapper = secretKeyWrapper;
    }

    public Util() {
    }

    /**
     * This Method loads the encrypted saved tokens, in the progress the Secret Key is unwrapped
     * and used to decrypt the saved tokens
     *
     * @return An ArrayList of Tokens
     */
    public ArrayList<Token> loadTokens() throws IOException, GeneralSecurityException {
        logprint("LOADING TOKEN");
        ArrayList<Token> tokens = new ArrayList<>();
        try {
            byte[] data = loadDataFromFile(DATAFILE);
            if (data == null) {
                return null;
            }
            JSONArray a = new JSONArray(new String(data));
            for (int i = 0; i < a.length(); i++) {
                tokens.add(makeTokenFromJSON(a.getJSONObject(i)));
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return tokens;
    }

    /**
     * Encrypt and save the ArrayList of tokens with a Secret Key, which is wrapped by a Public Key
     * that is stored in the Keystore
     *
     * @param tokens ArrayList of tokens to save
     */
    public void saveTokens(ArrayList<Token> tokens) throws GeneralSecurityException, IOException {
        if (tokens == null) {
            return;
        }
        JSONArray tmp = new JSONArray();
        for (Token t : tokens) {
            try {
                tmp.put(makeJSONfromToken(t));
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        if (saveToFile(DATAFILE, tmp.toString().getBytes())) {
            logprint("Tokenlist saved.");
        }
    }

    private Token makeTokenFromJSON(JSONObject o) throws JSONException {
        //Log.d("LOAD TOKEN FROM: ", o.toString());

        // when no serial is found (for "old" data) it is set to the label
        String serial;
        String label = o.getString(LABEL);
        try {
            serial = o.getString(SERIAL);
        } catch (JSONException e) {
            serial = label;
        }
        String type = o.getString(TYPE);

        if (type.equals(PUSH)) {
            Token t = new Token(serial, label);
            t.state = State.valueOf(o.getString(ROLLOUT_STATE));
            if (t.state.equals(UNFINISHED)) {
                t.rollout_url = o.getString(URL);
                t.enrollment_credential = o.getString(ENROLLMENT_CRED);
                t.sslVerify = o.getBoolean(SSL_VERIFY);
                try {
                    t.rollout_expiration = dateFormat.parse(o.getString(ROLLOUT_EXPIRATION));
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }
            // Check for pending Authentication Requests
            try {
                String pendingAuths = o.getString(PENDING_AUTHS);
                t.setPendingAuths(new Gson().fromJson(pendingAuths, new TypeToken<ArrayList<PushAuthRequest>>() {
                }.getType()));
            } catch (JSONException e) {
                // there were none and that's ok
            }
            return t;
        }

        Token tmp = new Token(new Base32().decode(o.getString(SECRET)), serial, label,
                type, o.getInt(DIGITS));

        tmp.setAlgorithm(o.getString(ALGORITHM));
        if (o.getString(TYPE).equals(HOTP)) {
            tmp.setCounter(o.getInt(COUNTER));
        }
        if (o.getString(TYPE).equals(TOTP)) {
            tmp.setPeriod(o.getInt(PERIOD));
        }
        if (o.optBoolean(WITHPIN, false)) {
            tmp.setWithPIN(true);
            tmp.setPin(o.getString(PIN));
            tmp.setLocked(true);
        }
        if (o.optBoolean(TAPTOSHOW, false)) {
            tmp.setWithTapToShow(true);
        }
        if (o.optBoolean(PERSISTENT)) {
            tmp.setPersistent(true);
        }

        return tmp;
    }

    private JSONObject makeJSONfromToken(Token t) throws JSONException {
        JSONObject o = new JSONObject();

        o.put(SERIAL, t.getSerial());
        o.put(LABEL, t.getLabel());
        o.put(TYPE, t.getType());

        if (t.getType().equals(PUSH)) {
            State state = t.state;
            if (state.equals(AUTHENTICATING)) {
                // Don't save authenticating state, has to be finished
                // Unfinished token cannot authenticate
                state = FINISHED;
            }
            o.put(ROLLOUT_STATE, state);
            // If the rollout is not finished yet, save the data necessary to complete it
            if (t.state.equals(UNFINISHED)) {
                o.put(URL, t.rollout_url);
                o.put(ROLLOUT_EXPIRATION, dateFormat.format(t.rollout_expiration));
                o.put(ENROLLMENT_CRED, t.enrollment_credential);
                o.put(SSL_VERIFY, t.sslVerify);
            }

            // Check for pending Authentication Requests
            if (!t.getPendingAuths().isEmpty()) {
                String pendingAuths = new Gson().toJson(t.getPendingAuths());
                o.put(PENDING_AUTHS, pendingAuths);
            }
            return o;
        }

        o.put(SECRET, new String(new Base32().encode(t.getSecret())));
        o.put(DIGITS, t.getDigits());
        o.put(ALGORITHM, t.getAlgorithm());

        if (t.getType().equals(HOTP)) {
            o.put(COUNTER, t.getCounter());
        }
        if (t.getType().equals(TOTP)) {
            o.put(PERIOD, t.getPeriod());
        }
        if (t.isWithPIN()) {
            o.put(WITHPIN, true);
            o.put(PIN, t.getPin());
        } else {
            o.put(WITHPIN, false);
        }
        if (t.isWithTapToShow()) {
            o.put(TAPTOSHOW, true);
        }
        if (t.isPersistent()) {
            o.put(PERSISTENT, true);
        }
        return o;
    }

    public void storePIPubkey(String key, String serial) throws GeneralSecurityException, IllegalArgumentException, IOException {
        byte[] keybytes = decodeBase64(key);

        PublicKey pubkey = PKCS1ToSubjectPublicKeyInfo.decodePKCS1PublicKey(keybytes);
        if (saveToFile(serial + "_" + PUBKEYFILE, pubkey.getEncoded())) {
            logprint("pubkey for " + serial + " saved.");
        }

        // this code is expecting pkcs8
        /*X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keybytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PublicKey pubkey = kf.generatePublic(keySpec); */
    }

    public PublicKey getPIPubkey(String serial) throws GeneralSecurityException, IOException {
        if (baseFilePath == null) return null;
        return getPIPubkey(baseFilePath, serial);
    }

    PublicKey getPIPubkey(String filepath, String serial) throws GeneralSecurityException, IOException {
        byte[] keybytes = loadDataFromFile(serial + "_" + PUBKEYFILE, filepath);
        // build pubkey
        if (keybytes == null) return null;
        X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(keybytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePublic(X509publicKey);
    }

    /**
     * Load the data from an encrypted file. The baseFilePath of Util will be used if set.
     * baseFilePath + "/" + fileName
     *
     * @param fileName Name of the file to load
     * @return raw data as byte array, null if no baseFilePath is set or there is no file
     */
    private byte[] loadDataFromFile(String fileName) throws IOException, GeneralSecurityException {
        if (baseFilePath == null) return null;
        return loadDataFromFile(fileName, baseFilePath);
    }

    /**
     * Load the data from an encrypted file, using the specified baseFilePath (from context).
     * baseFilePath + "/" + fileName
     *
     * @param fileName     Name of the file to load
     * @param baseFilePath baseFilePath of the Context
     * @return raw data as byte array, null if there is no file
     */
    private byte[] loadDataFromFile(String fileName, String baseFilePath) throws IOException, GeneralSecurityException {
        byte[] encryptedData = readFile(new File(baseFilePath + "/" + fileName));
        // decrypt
        SecretKey encryptionKey = getSecretKey(new File(baseFilePath + "/" + KEYFILE));
        if (encryptedData == null) {
            return null;
        }
        return decrypt(encryptionKey, encryptedData);
    }

    private void writeFile(File file, byte[] data) throws IOException {
        try (OutputStream out = new FileOutputStream(file)) {
            out.write(data);
        }
    }

    private byte[] readFile(File file) throws IOException {
        try (InputStream in = new FileInputStream(file)) {
            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int count;
            while ((count = in.read(buffer)) != -1) {
                bytes.write(buffer, 0, count);
            }
            return bytes.toByteArray();
        } catch (FileNotFoundException e) {
            logprint("File: " + file.getAbsolutePath() + " not found");
            return null;
        }
    }

    public void removePubkeyFor(String serial) {
        File f = new File(baseFilePath + "/" + serial + "_" + PUBKEYFILE);
        boolean res;
        if (f.exists()) {
            res = f.delete();
            if (res) {
                logprint("pubkey file of " + serial + " was found and deleted!");
            } else {
                logprint("pubkey file of " + serial + " was not deleted!");
            }
        } else {
            logprint("pubkey file of " + serial + " was not found!");
        }
    }

    public void storeFirebaseConfig(FirebaseInitConfig firebaseInitConfig) throws GeneralSecurityException, IOException {
        logprint("Storing Firebase config...");
        JSONObject o = new JSONObject();
        try {
            o.put(PROJECT_ID, firebaseInitConfig.getProjID());
            o.put(APP_ID, firebaseInitConfig.getAppID());
            o.put(API_KEY, firebaseInitConfig.getApiKey());
            o.put(PROJECT_NUMBER, firebaseInitConfig.getProjNumber());
        } catch (JSONException e) {
            e.printStackTrace();
        }
        if (saveToFile(FB_CONFIG_FILE, o.toString().getBytes())) {
            logprint("Firebase config stored.");
        }
    }

    /**
     * @return FirebaseInitConfig object or null if there is no config / error
     */
    public FirebaseInitConfig loadFirebaseConfig() throws IOException, GeneralSecurityException, JSONException {
        logprint("Loading Firebase config...");
        byte[] data = loadDataFromFile(FB_CONFIG_FILE);
        if (data == null) {
            logprint("Firebase config not found!");
            return null;
        }

        JSONObject o = new JSONObject(new String(data));
        String projID = o.getString(PROJECT_ID);
        String appID = o.getString(APP_ID);
        String api_key = o.getString(API_KEY);
        String projNumber = o.getString(PROJECT_NUMBER);

        //logprint("Firebase config loaded.");
        return new FirebaseInitConfig(projID, appID, api_key, projNumber);
    }

    public void removeFirebaseConfig() {
        File f = new File(baseFilePath + "/" + FB_CONFIG_FILE);
        if (f.exists()) {
            if (f.delete()) {
                logprint("Firebase config removed.");
            }
        }
    }

    /**
     * Encrypt and save the given data in the specified file and baseFilePath.
     * baseFilePath + "/" + fileName
     *
     * @param fileName     Name of the file to save to
     * @param baseFilePath Path to the app's data storage
     * @param data         Data to save
     * @return true if successful, false if error
     */
    private boolean saveToFile(String fileName, String baseFilePath, byte[] data) throws GeneralSecurityException, IOException {
        SecretKey key = getSecretKey(new File(baseFilePath + "/" + KEYFILE));
        data = encrypt(key, data);
        writeFile(new File(baseFilePath + "/" + fileName), data);
        return true;
    }

    /**
     * Encrypt and save the given data in the specified file.
     * baseFilePath + "/" + fileName
     *
     * @param fileName Name of the file to save to
     * @param data     Data to save
     * @return true if successful, false if error
     */
    public boolean saveToFile(String fileName, byte[] data) throws GeneralSecurityException, IOException {
        if (baseFilePath == null) return false;
        return saveToFile(fileName, baseFilePath, data);
    }

    private static byte[] encrypt(SecretKey secretKey, GCMParameterSpec iv, byte[] plainText)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
            InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance(CRYPT_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
        return cipher.doFinal(plainText);
    }

    private static byte[] decrypt(SecretKey secretKey, GCMParameterSpec iv, byte[] cipherText)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
            InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        Cipher cipher = Cipher.getInstance(CRYPT_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
        return cipher.doFinal(cipherText);
    }

    static byte[] encrypt(SecretKey secretKey, byte[] plaintext)
            throws NoSuchPaddingException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException,
            IllegalBlockSizeException, InvalidAlgorithmParameterException {
        final byte[] iv = new byte[IV_LENGTH];
        new SecureRandom().nextBytes(iv);
        GCMParameterSpec params = new GCMParameterSpec(128, iv, 0, 12);
        byte[] cipherText = encrypt(secretKey, params, plaintext);
        byte[] combined = new byte[iv.length + cipherText.length];
        System.arraycopy(iv, 0, combined, 0, iv.length);
        System.arraycopy(cipherText, 0, combined, iv.length, cipherText.length);
        return combined;
    }

    static byte[] decrypt(SecretKey secretKey, byte[] cipherText)
            throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException,
            BadPaddingException, InvalidAlgorithmParameterException {
        //byte[] iv = Arrays.copyOfRange(cipherText, 0, IV_LENGTH);
        GCMParameterSpec params = new GCMParameterSpec(128, cipherText, 0, 12);
        byte[] cipher = Arrays.copyOfRange(cipherText, IV_LENGTH, cipherText.length);
        return decrypt(secretKey, params, cipher);
    }

    /**
     * Load our symmetric secret key.
     * The symmetric secret key is stored securely on disk by wrapping
     * it with a public/private key pair, possibly backed by hardware.
     */
    public SecretKey getSecretKey(File keyFile)
            throws GeneralSecurityException, IOException {
        if (secretKeyWrapper == null) {
            throw new GeneralSecurityException("No SecretKeyWrapper available!");
        }
        // Generate secret key if none exists
        if (!keyFile.exists()) {
            final byte[] raw = new byte[KEY_LENGTH];
            new SecureRandom().nextBytes(raw);
            final SecretKey key = new SecretKeySpec(raw, "AES");
            final byte[] wrapped = secretKeyWrapper.wrap(key);
            writeFile(keyFile, wrapped);
        }
        // Even if we just generated the key, always read it back to ensure we
        // can read it successfully.
        final byte[] wrapped = readFile(keyFile);
        if (wrapped == null) return null;
        return secretKeyWrapper.unwrap(wrapped);
    }

    /**
     * @param privateKey privateKey to sign the message with
     * @param message    message to sign
     * @return Base32 formatted signature
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws SignatureException
     */
    public static String sign(PrivateKey privateKey, String message) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        byte[] bMessage = message.getBytes(StandardCharsets.UTF_8);

        Signature s = Signature.getInstance(SIGNING_ALGORITHM);
        s.initSign(privateKey);
        s.update(bMessage);

        byte[] signature = s.sign();
        return new Base32().encodeAsString(signature);
    }

    /**
     * @param publicKey publicKey to verify the signature with
     * @param signature signature to verify, !!formatted in Base32!!
     * @param payload   payload that was signed
     * @return true if the signature is valid, false otherwise
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws SignatureException
     */
    public static boolean verifySignature(PublicKey publicKey, String signature, String payload) throws InvalidKeyException,
            NoSuchAlgorithmException, SignatureException {
        if (!new Base32().isInAlphabet(signature)) {
            logprint("verifySignature: The given signature is not Base32 encoded!");
            return false;
        }

        byte[] message = payload.getBytes(StandardCharsets.UTF_8);
        byte[] bSignature = new Base32().decode(signature);
        Signature sig = Signature.getInstance(SIGNING_ALGORITHM);

        sig.initVerify(publicKey);
        sig.update(message);
        return sig.verify(bSignature);
    }

    public byte[] decodeBase64(String key) {
        return Base64.decode(key, Base64.DEFAULT);
    }

    public String encodeBase64(byte[] data) {
        return Base64.encodeToString(data, Base64.URL_SAFE);
    }

    /**
     * Converts a byte array to a Hex String
     *
     * @param ba byte array to convert
     * @return the Hex as String
     */
    public static String byteArrayToHexString(byte[] ba) {
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < ba.length; i++)
            str.append(String.format("%02x", ba[i]));
        return str.toString();
    }

    /**
     * Converts a Hex string to a byte array
     *
     * @param hex: the Hex string to convert
     * @return a byte array
     */
    public static byte[] hexStringToByteArray(String hex) {
        // Adding one byte to get the right conversion
        // Values starting with "0" can be converted
        byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();

        // Copy all the REAL bytes, not the "first"
        byte[] ret = new byte[bArray.length - 1];
        for (int i = 0; i < ret.length; i++)
            ret[i] = bArray[i + 1];
        return ret;
    }

    public static void logprint(String msg) {
        if (BuildConfig.DEBUG) {
            if (msg == null)
                return;
            Log.e(TAG, msg);
        }
    }

    public static String insertPeriodically(String text, int stepSize) {
        StringBuilder builder = new StringBuilder(text.length() + " ".length() * (text.length() / stepSize) + 1);
        int index = 0;
        String prefix = "";
        while (index < text.length()) {
            builder.append(prefix);
            prefix = " ";
            builder.append(text.substring(index,
                    Math.min(index + stepSize, text.length())));
            index += stepSize;
        }
        return builder.toString();
    }
} public static final String ALGORITHM = "algorithm"; public static final String API_KEY = "apikey"; public static final String APP_ID = "appid"; public static final String CHANNEL_ID_HIGH_PRIO = "privacyIDEAPush_high"; public static final String CHANNEL_ID_LOW_PRIO = "privacyIDEAPush_low"; public static final String COUNTER = "counter"; public static final String DIGITS = "digits"; public static final String ENROLLMENT_CRED = "enrollment_credential"; public static final String HOTP = "hotp"; public static final int INTENT_ADD_TOKEN_MANUALLY = 101; public static final String ISSUER = "issuer"; public static final String LABEL = "label"; public static final String NONCE = "nonce"; public static final String NOTIFICATION_ID = "notificationID"; public static final String PERIOD = "period"; public static final String PERSISTENT = "persistent"; public static final String PIN = "pin"; public static final String PROJECT_ID = "projectid"; public static final String PROJECT_NUMBER = "projectnumber"; public static final String PUSH = "pipush"; public static final String PUSH_VERSION = "v"; public static final String QUESTION = "question"; public static final String SECRET = "secret"; public static final String SERIAL = "serial"; public static final String SIGNATURE = "signature"; public static final String SSL_VERIFY = "sslverify"; public static final String TAPTOSHOW = "taptoshow"; public static final String TITLE = "title"; public static final String TOTP = "totp"; public static final String TTL = "ttl"; public static final String TWOSTEP_DIFFICULTY = "2step_difficulty";    // PBKDF2 iterations public static final String TWOSTEP_OUTPUT = "2step_output";            // size of the key generated by PBKDF2 public static final String TWOSTEP_SALT = "2step_salt";                // Size of the random bytes generated by the smartphone, which are used as salt for PBKDF2 public static final String TYPE = "type"; public static final String URL = "url"; public static final String WITHPIN = "withpin"; public static void logprint(String msg) {
    if (BuildConfig.DEBUG) {
        if (msg == null)
            return;
        Log.e(TAG, msg);
    }
}
import android.app.Activity;
import android.app.AlertDialog;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.text.Html;
import android.text.InputType;
import android.text.Spanned;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.NotificationManagerCompat;
import androidx.lifecycle.LifecycleObserver;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import it.netknights.piauthenticator.R;
import it.netknights.piauthenticator.interfaces.MainActivityInterface;
import it.netknights.piauthenticator.interfaces.PresenterInterface;
import it.netknights.piauthenticator.model.FirebaseInitConfig;
import it.netknights.piauthenticator.model.PushAuthRequest;
import it.netknights.piauthenticator.model.ScanResult;
import it.netknights.piauthenticator.model.Token;
import it.netknights.piauthenticator.presenter.Presenter;
import it.netknights.piauthenticator.utils.AppConstants;
import it.netknights.piauthenticator.utils.SecretKeyWrapper;
import it.netknights.piauthenticator.utils.Util;
import static it.netknights.piauthenticator.R.color.PIBLUE;
import static it.netknights.piauthenticator.utils.AppConstants.ALGORITHM;
import static it.netknights.piauthenticator.utils.AppConstants.API_KEY;
import static it.netknights.piauthenticator.utils.AppConstants.APP_ID;
import static it.netknights.piauthenticator.utils.AppConstants.CHANNEL_ID_HIGH_PRIO;
import static it.netknights.piauthenticator.utils.AppConstants.CHANNEL_ID_LOW_PRIO;
import static it.netknights.piauthenticator.utils.AppConstants.COUNTER;
import static it.netknights.piauthenticator.utils.AppConstants.DIGITS;
import static it.netknights.piauthenticator.utils.AppConstants.ENROLLMENT_CRED;
import static it.netknights.piauthenticator.utils.AppConstants.HOTP;
import static it.netknights.piauthenticator.utils.AppConstants.INTENT_ADD_TOKEN_MANUALLY;
import static it.netknights.piauthenticator.utils.AppConstants.ISSUER;
import static it.netknights.piauthenticator.utils.AppConstants.LABEL;
import static it.netknights.piauthenticator.utils.AppConstants.NONCE;
import static it.netknights.piauthenticator.utils.AppConstants.NOTIFICATION_ID;
import static it.netknights.piauthenticator.utils.AppConstants.PERIOD;
import static it.netknights.piauthenticator.utils.AppConstants.PERSISTENT;
import static it.netknights.piauthenticator.utils.AppConstants.PIN;
import static it.netknights.piauthenticator.utils.AppConstants.PROJECT_ID;
import static it.netknights.piauthenticator.utils.AppConstants.PROJECT_NUMBER;
import static it.netknights.piauthenticator.utils.AppConstants.PUSH;
import static it.netknights.piauthenticator.utils.AppConstants.PUSH_VERSION;
import static it.netknights.piauthenticator.utils.AppConstants.QUESTION;
import static it.netknights.piauthenticator.utils.AppConstants.SECRET;
import static it.netknights.piauthenticator.utils.AppConstants.SERIAL;
import static it.netknights.piauthenticator.utils.AppConstants.SIGNATURE;
import static it.netknights.piauthenticator.utils.AppConstants.SSL_VERIFY;
import static it.netknights.piauthenticator.utils.AppConstants.TAPTOSHOW;
import static it.netknights.piauthenticator.utils.AppConstants.TITLE;
import static it.netknights.piauthenticator.utils.AppConstants.TOTP;
import static it.netknights.piauthenticator.utils.AppConstants.TTL;
import static it.netknights.piauthenticator.utils.AppConstants.TWOSTEP_DIFFICULTY;
import static it.netknights.piauthenticator.utils.AppConstants.TWOSTEP_OUTPUT;
import static it.netknights.piauthenticator.utils.AppConstants.TWOSTEP_SALT;
import static it.netknights.piauthenticator.utils.AppConstants.TYPE;
import static it.netknights.piauthenticator.utils.AppConstants.URL;
import static it.netknights.piauthenticator.utils.AppConstants.WITHPIN;
import static it.netknights.piauthenticator.utils.Util.logprint;
/*
  privacyIDEA Authenticator

  Authors: Nils Behlen <nils.behlen@netknights.it>

  Copyright (c) 2017-2019 NetKnights GmbH

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

package it.netknights.piauthenticator.viewcontroller;







public class MainActivity extends AppCompatActivity implements ActionMode.Callback, MainActivityInterface, LifecycleObserver {
    private PresenterInterface presenterInterface;
    private TokenListAdapter tokenlistadapter;
    private ListView listview;
    private AlertDialog status_dialog;
    private Handler handler;
    private Runnable timer;
    private MainActivityBroadcastReceiver receiver;

    // getting the firebase token requires the Activity
    private SecretKeyWrapper secretKeyWrapper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setupViews();
        setupFab();

        // SETUP INTERFACES AND DEPENDENCIES
        tokenlistadapter = new TokenListAdapter();
        secretKeyWrapper = null;
        try {
            secretKeyWrapper = new SecretKeyWrapper(this);
        } catch (GeneralSecurityException | IllegalStateException | IOException e) {          // TODO handle e
            e.printStackTrace();
            makeDeviceNotSupportedDialog(e);
        }

        Util util = new Util(secretKeyWrapper, getFilesDir().getAbsolutePath());
        Presenter presenter = new Presenter(tokenlistadapter, this, util);

        presenterInterface = presenter;

        // this method checks if saving keys works, if not a dialog will appear informing the user
        // that the application cannot be used on the device
        //presenter.checkKeyStoreIsWorking();

        tokenlistadapter.setPresenterInterface(presenter);

        // init the model before the adapter is set
        presenterInterface.init();
        listview.setAdapter(tokenlistadapter);
        receiver = new MainActivityBroadcastReceiver(this);
        registerReceiver(receiver, receiver.intentFilter);

        createNotificationChannels();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            paintStatusbar();
        }

        // Check for intent (=Pending auth requests)
        Intent intent = getIntent();
        if (intent.getExtras() == null) {
            logprint("No intent found onCreate.");
        } else {
            pushAuthRequestReceived(intent);
        }

        getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);

    }

    void pushAuthRequestReceived(Intent intent) {
        // intent contain push auth info
        logprint("Intent found onCreate: " + ((intent.getExtras() != null) ? intent.getExtras().toString() : "No extras."));
        String serial = intent.getStringExtra(SERIAL);
        String nonce = intent.getStringExtra(NONCE);
        String title = intent.getStringExtra(TITLE);
        String url = intent.getStringExtra(URL);
        String signature = intent.getStringExtra(SIGNATURE);
        String question = intent.getStringExtra(QUESTION);
        int notificationID = intent.getIntExtra(NOTIFICATION_ID, 654321);
        boolean sslVerify = intent.getBooleanExtra(SSL_VERIFY, !"0".equals(intent.getStringExtra(SSL_VERIFY)));