- Added Pocket Security Functionality (still in progress...)

This commit is contained in:
Javier Varona 2018-07-26 22:58:12 -04:00
parent 5b21c0719e
commit 6117fc0f99
14 changed files with 429 additions and 6 deletions

View file

@ -1,4 +1,5 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 27
@ -79,4 +80,6 @@ dependencies {
implementation 'id.zelory:compressor:2.1.0'
implementation 'com.vincent.filepicker:MultiTypeFilePicker:1.0.7'
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
implementation 'commons-codec:commons-codec:1.11'
}

View file

@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.NFC" />
<application
android:name=".application.CrystalApplication"
@ -52,6 +53,21 @@
<activity android:name=".activities.PatternRequestActivity"
android:noHistory="true">
</activity>
<activity android:name=".activities.PocketRequestActivity"
android:noHistory="true">
<!--<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/tech" />-->
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data
android:scheme="https"
android:host="my.yubico.com"
android:pathPrefix="/neo"/>
</intent-filter>
</activity>
<activity android:name=".activities.CryptoNetAccountSettingsActivity"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustPan">
@ -60,6 +76,14 @@
android:name=".activities.SettingsActivity"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustPan">
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT"/>
<data
android:scheme="https"
android:host="my.yubico.com"
android:pathPrefix="/neo"/>
</intent-filter>
</activity>
<activity
android:name=".activities.AccountsActivity"

View file

@ -95,6 +95,7 @@ public class IntroActivity extends AppCompatActivity {
} else {
//Intent intent = new Intent(this, CreateSeedActivity.class);
Intent intent = new Intent(this, BoardActivity.class);
//Intent intent = new Intent(this, PocketRequestActivity.class);
startActivity(intent);
}

View file

@ -0,0 +1,130 @@
package cy.agorise.crystalwallet.activities;
import android.app.PendingIntent;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.NdefFormatable;
import android.nfc.tech.NfcA;
import android.nfc.tech.NfcF;
import android.nfc.tech.NfcV;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;
import com.andrognito.patternlockview.PatternLockView;
import com.andrognito.patternlockview.listener.PatternLockViewListener;
import org.apache.commons.codec.binary.Base32;
import java.io.IOException;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import cy.agorise.crystalwallet.R;
import cy.agorise.crystalwallet.models.GeneralSetting;
import cy.agorise.crystalwallet.util.PasswordManager;
import cy.agorise.crystalwallet.util.yubikey.Algorithm;
import cy.agorise.crystalwallet.util.yubikey.OathType;
import cy.agorise.crystalwallet.util.yubikey.YkOathApi;
import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel;
public class PocketRequestActivity extends AppCompatActivity {
private NfcAdapter mNfcAdapter;
private PendingIntent pendingIntent;
private IntentFilter ndef;
IntentFilter[] intentFiltersArray;
String[][] techList;
@Override
public void onBackPressed() {
//Do nothing to prevent the user to use the back button
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pocket_request);
ButterKnife.bind(this);
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
this.configureForegroundDispatch();
}
public void configureForegroundDispatch(){
if (mNfcAdapter != null) {
pendingIntent = PendingIntent.getActivity(
this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
ndef.addDataType("*/*"); /* Handles all MIME based dispatches.
You should specify only the ones that you need. */
} catch (IntentFilter.MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
intentFiltersArray = new IntentFilter[]{ndef,};
techList = new String[][]{ new String[] {IsoDep.class.getName(), NfcA.class.getName(), MifareClassic.class.getName(), NdefFormatable.class.getName()} };
} else {
Toast.makeText(this, "This device doesn't support NFC.", Toast.LENGTH_LONG).show();
}
}
public void onPause() {
super.onPause();
if (mNfcAdapter != null) {
mNfcAdapter.disableForegroundDispatch(this);
}
}
public void onResume() {
super.onResume();
if (mNfcAdapter != null) {
mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techList);
}
}
public void onNewIntent(Intent intent) {
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
IsoDep tagIsoDep = IsoDep.get(tagFromIntent);
Log.i("Tag from nfc","New Intent");
try {
String encodedSecret = "hola";
Base32 decoder = new Base32();
if ((encodedSecret != null) && (!encodedSecret.equals("")) && decoder.isInAlphabet(encodedSecret)) {
byte[] secret = decoder.decode(encodedSecret);
YkOathApi ykOathApi = new YkOathApi();
tagIsoDep.connect();
tagIsoDep.setTimeout(15000);
//byte[] keyBytes = {0x68,0x6f,0x6c,0x61};
ykOathApi.putCode(tagIsoDep,"prueba",secret, OathType.TOTP, Algorithm.SHA256,(byte)6,0,false);
tagIsoDep.close();
Toast.makeText(this, "Credential saved!", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "Invalid password for credential", Toast.LENGTH_LONG).show();
}
} catch (IOException e) {
e.printStackTrace();
}
Toast.makeText(this, "Tag from nfc: "+tagFromIntent, Toast.LENGTH_LONG).show();
}
}

View file

@ -1,5 +1,6 @@
package cy.agorise.crystalwallet.activities;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@ -47,6 +48,8 @@ public class SettingsActivity extends AppCompatActivity{
@BindView(R.id.tvBuildVersion)
public TextView tvBuildVersion;
private SecuritySettingsFragment securitySettingsFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -97,7 +100,8 @@ public class SettingsActivity extends AppCompatActivity{
case 0:
return new GeneralSettingsFragment();
case 1:
return new SecuritySettingsFragment();
securitySettingsFragment = new SecuritySettingsFragment();
return securitySettingsFragment;
case 2:
return new BackupsSettingsFragment();
//case 3:
@ -123,4 +127,12 @@ public class SettingsActivity extends AppCompatActivity{
public void goBack(){
onBackPressed();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (this.securitySettingsFragment != null){
this.securitySettingsFragment.onNewIntent(intent);
}
}
}

View file

@ -27,6 +27,7 @@ public class CrystalSecurityMonitor implements Application.ActivityLifecycleCall
private int numStarted = 0;
private String passwordEncrypted;
private String patternEncrypted;
private String yubikeyOathTotpPasswordEncrypted;
private static CrystalSecurityMonitor instance;
private GeneralSettingListViewModel generalSettingListViewModel;
@ -38,8 +39,10 @@ public class CrystalSecurityMonitor implements Application.ActivityLifecycleCall
this.passwordEncrypted = "";
this.patternEncrypted = "";
this.yubikeyOathTotpPasswordEncrypted = "";
GeneralSetting passwordGeneralSetting = generalSettingListViewModel.getGeneralSettingByName(GeneralSetting.SETTING_PASSWORD);;
GeneralSetting patternGeneralSetting = generalSettingListViewModel.getGeneralSettingByName(GeneralSetting.SETTING_PATTERN);;
GeneralSetting yubikeyOathTotpPasswordSetting = generalSettingListViewModel.getGeneralSettingByName(GeneralSetting.SETTING_YUBIKEY_OATH_TOTP_PASSWORD);;
if (passwordGeneralSetting != null){
this.passwordEncrypted = passwordGeneralSetting.getValue();
@ -47,6 +50,9 @@ public class CrystalSecurityMonitor implements Application.ActivityLifecycleCall
if (patternGeneralSetting != null){
this.patternEncrypted = patternGeneralSetting.getValue();
}
if (yubikeyOathTotpPasswordSetting != null){
this.yubikeyOathTotpPasswordEncrypted = yubikeyOathTotpPasswordSetting.getValue();
}
}
public static CrystalSecurityMonitor getInstance(final FragmentActivity fragmentActivity){
@ -57,6 +63,10 @@ public class CrystalSecurityMonitor implements Application.ActivityLifecycleCall
return instance;
}
public static String getServiceName(){
return "cy.agorise.crystalwallet";
}
public void clearSecurity(){
this.patternEncrypted = "";
this.passwordEncrypted = "";
@ -65,6 +75,12 @@ public class CrystalSecurityMonitor implements Application.ActivityLifecycleCall
generalSettingListViewModel.deleteGeneralSettingByName(GeneralSetting.SETTING_PATTERN);
}
public void clearSecurity2ndFactor(){
this.yubikeyOathTotpPasswordEncrypted = "";
generalSettingListViewModel.deleteGeneralSettingByName(GeneralSetting.SETTING_YUBIKEY_OATH_TOTP_PASSWORD);
}
public void setPasswordSecurity(String password){
clearSecurity();
this.passwordEncrypted = password;
@ -95,6 +111,15 @@ public class CrystalSecurityMonitor implements Application.ActivityLifecycleCall
return "";
}
public void setYubikeyOathTotpSecurity(String name, String password){
this.yubikeyOathTotpPasswordEncrypted = password;
GeneralSetting yubikeyOathTotpSetting = new GeneralSetting();
yubikeyOathTotpSetting.setName(GeneralSetting.SETTING_YUBIKEY_OATH_TOTP_PASSWORD);
yubikeyOathTotpSetting.setValue(password);
generalSettingListViewModel.saveGeneralSetting(yubikeyOathTotpSetting);
}
@Override
public void onActivityStarted(Activity activity) {
if (numStarted == 0) {

View file

@ -1,20 +1,42 @@
package cy.agorise.crystalwallet.fragments;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.NdefFormatable;
import android.nfc.tech.NfcA;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Switch;
import android.widget.Toast;
import org.apache.commons.codec.binary.Base32;
import java.io.IOException;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnCheckedChanged;
import cy.agorise.crystalwallet.R;
import cy.agorise.crystalwallet.application.CrystalSecurityMonitor;
import cy.agorise.crystalwallet.models.GeneralSetting;
import cy.agorise.crystalwallet.util.ChildViewPager;
import cy.agorise.crystalwallet.util.yubikey.Algorithm;
import cy.agorise.crystalwallet.util.yubikey.OathType;
import cy.agorise.crystalwallet.util.yubikey.YkOathApi;
/**
* Created by xd on 1/17/18.
@ -34,11 +56,23 @@ public class SecuritySettingsFragment extends Fragment {
return fragment;
}
private NfcAdapter mNfcAdapter;
private PendingIntent pendingIntent;
private IntentFilter ndef;
IntentFilter[] intentFiltersArray;
String[][] techList;
@BindView(R.id.pager)
public ChildViewPager mPager;
public SecurityPagerAdapter securityPagerAdapter;
@BindView(R.id.sPocketSecurity)
Switch sPocketSecurity;
@BindView(R.id.etPocketPassword)
EditText etPocketPassword;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@ -66,6 +100,9 @@ public class SecuritySettingsFragment extends Fragment {
mPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(mPager));
mNfcAdapter = NfcAdapter.getDefaultAdapter(this.getActivity());
this.configureForegroundDispatch();
return v;
}
@ -101,4 +138,87 @@ public class SecuritySettingsFragment extends Fragment {
return 3;
}
}
@OnCheckedChanged(R.id.sPocketSecurity)
public void onPocketSecurityActivated(CompoundButton button, boolean checked){
if (checked) {
enableNfc();
} else {
disableNfc();
}
}
public void configureForegroundDispatch(){
if (mNfcAdapter != null) {
pendingIntent = PendingIntent.getActivity(
this.getActivity(), 0, new Intent(this.getActivity(), getActivity().getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
ndef.addDataType("*/*"); /* Handles all MIME based dispatches.
You should specify only the ones that you need. */
} catch (IntentFilter.MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
intentFiltersArray = new IntentFilter[]{ndef,};
techList = new String[][]{ new String[] {IsoDep.class.getName(), NfcA.class.getName(), MifareClassic.class.getName(), NdefFormatable.class.getName()} };
} else {
Toast.makeText(this.getContext(), "This device doesn't support NFC.", Toast.LENGTH_LONG).show();
}
}
public void enableNfc(){
mNfcAdapter.enableForegroundDispatch(this.getActivity(), pendingIntent, intentFiltersArray, techList);
Toast.makeText(this.getContext(), "Tap with your yubikey", Toast.LENGTH_LONG).show();
}
public void disableNfc(){
mNfcAdapter.disableForegroundDispatch(this.getActivity());
}
public void onPause() {
super.onPause();
disableNfc();
}
public void onResume() {
super.onResume();
if (mNfcAdapter != null) {
enableNfc();
}
}
public void onNewIntent(Intent intent) {
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
IsoDep tagIsoDep = IsoDep.get(tagFromIntent);
Log.i("Tag from nfc","New Intent");
try {
String serviceName = "cy.agorise.crystalwallet";
String encodedSecret = etPocketPassword.getText().toString();
Base32 decoder = new Base32();
if ((encodedSecret != null) && (!encodedSecret.equals("")) && decoder.isInAlphabet(encodedSecret)) {
byte[] secret = decoder.decode(encodedSecret);
YkOathApi ykOathApi = new YkOathApi();
tagIsoDep.connect();
tagIsoDep.setTimeout(15000);
try {
ykOathApi.putCode(tagIsoDep, serviceName, secret, OathType.TOTP, Algorithm.SHA256, (byte) 6, 0, false);
CrystalSecurityMonitor.getInstance(null).setYubikeyOathTotpSecurity(CrystalSecurityMonitor.getServiceName(),encodedSecret);
} catch(IOException e) {
Toast.makeText(this.getContext(), "There's no space for new credentials!", Toast.LENGTH_LONG).show();
}
tagIsoDep.close();
Toast.makeText(this.getContext(), "Credential saved!", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this.getContext(), "Invalid password for credential", Toast.LENGTH_LONG).show();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

View file

@ -23,6 +23,8 @@ public class GeneralSetting {
public final static String SETTING_PATTERN = "PATTERN";
public final static String SETTING_NAME_RECEIVED_FUNDS_SOUND_PATH = "RECEIVED_FUNDS_SOUND_PATH";
public final static String SETTING_LAST_LICENSE_READ = "LAST_LICENSE_READ";
public final static String SETTING_YUBIKEY_OATH_TOTP_NAME = "YUBIKEY_OATH_TOTP_NAME";
public final static String SETTING_YUBIKEY_OATH_TOTP_PASSWORD = "YUBIKEY_OATH_TOTP_PASSWORD";
/**
* The id on the database

View file

@ -0,0 +1,58 @@
package cy.agorise.crystalwallet.util.yubikey;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import kotlin.jvm.internal.Intrinsics;
public enum Algorithm {
SHA1((byte)1, "SHA1", 64),
SHA256((byte)2, "SHA256", 64),
SHA512((byte)3, "SHA512", 128);
protected Byte byteVal;
protected String name;
protected int blockSize;
protected MessageDigest messageDigest;
Algorithm(Byte byteVal, String name, int blockSize){
this.byteVal = byteVal;
this.name = name;
this.blockSize = blockSize;
try {
this.messageDigest = MessageDigest.getInstance(name);
} catch (NoSuchAlgorithmException e){
this.messageDigest = null;
}
}
public Byte getByteVal(){
return this.byteVal;
}
public String getName(){
return this.name;
}
public int getBlockSize() {
return this.blockSize;
}
public byte[] prepareKey(byte[] key){
int keyLength = key.length;
byte[] keyPrepared;
if ((0 <= keyLength) && (keyLength <= 13)) {
keyPrepared = Arrays.copyOf(key, 14);
return keyPrepared;
}
if ((14 <= keyLength) && (keyLength <= this.blockSize)){
keyPrepared = key;
return keyPrepared;
}
keyPrepared = this.messageDigest.digest(key);
return keyPrepared;
}
}

View file

@ -0,0 +1,16 @@
package cy.agorise.crystalwallet.util.yubikey;
public enum OathType {
HOTP((byte)0x10),
TOTP((byte)0x20);
protected Byte byteVal;
OathType(Byte byteVal){
this.byteVal = byteVal;
}
public Byte getByteVal(){
return this.byteVal;
}
}

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:background="@color/colorPrimary">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Please tap pocket!"/>
</LinearLayout>
</LinearLayout>

View file

@ -68,7 +68,7 @@
android:layout_width="match_parent"
android:layout_height="140dp"
android:background="@color/lightGray"
android:visibility="gone"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
@ -102,15 +102,14 @@
android:text="@string/user_name_password_placeholder"
app:layout_constraintTop_toBottomOf="@id/tvPocketSecurity"
app:layout_constraintStart_toStartOf="@id/tvPocketSecurity"/>
<EditText
android:id="@+id/etPocketPassword"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="numberPassword"
app:layout_constraintTop_toBottomOf="@id/tvPocketSecurityUser"
app:layout_constraintStart_toStartOf="@id/tvPocketSecurity"
app:layout_constraintEnd_toEndOf="@id/sPocketSecurity"
app:layout_constraintStart_toStartOf="@id/tvPocketSecurity"
app:layout_constraintTop_toBottomOf="@id/tvPocketSecurityUser"
tools:ignore="LabelFor" />
</android.support.constraint.ConstraintLayout>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.MifareUltralight</tech>
<tech>android.nfc.tech.Ndef</tech>
<tech>android.nfc.tech.NfcA</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.MifareClassic</tech>
<tech>android.nfc.tech.Ndef</tech>
<tech>android.nfc.tech.NfcA</tech>
</tech-list>
</resources>

View file

@ -5,9 +5,11 @@ buildscript {
jcenter()
google()
}
ext.kotlin_version = '1.2.51'
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}