save tmp
This commit is contained in:
parent
2fc94d2a56
commit
2fd2850fbd
File diff suppressed because one or more lines are too long
@ -12,13 +12,14 @@ function promiseCb(funId, promiseFun, dataParser) {
|
||||
}
|
||||
/**
|
||||
* oauth login before init internal wallet
|
||||
* @param {*} channel 0: google, 1: apple, 2: tiktok, 3: facebook, 4: twitter 5: tg
|
||||
* @param {*} channel 0: google, 1: apple, 2: tiktok, 3: facebook, 4: twitter 5: tg, 6: email, 7: discord 10: client
|
||||
*/
|
||||
function walletLogin(funId, channel) {
|
||||
function walletLogin(funId, channel, env) {
|
||||
channel = parseInt(channel);
|
||||
env = env || 'dev';
|
||||
console.log('walletLogin: ' + channel);
|
||||
const wallet = !window.jc || !jc.wallet ? new jcwallet.default({ type: 0 }) : jc.wallet;
|
||||
promiseCb(funId, wallet.preLogin(channel));
|
||||
promiseCb(funId, wallet.preLogin(channel, env));
|
||||
}
|
||||
/**
|
||||
* init internal wallet with password
|
||||
@ -587,3 +588,15 @@ function restorePassLocal(funId, key) {
|
||||
function getLocalPassState(funId, key) {
|
||||
promiseCb(funId, jc.wallet.nativeSvr.passStorageState(key));
|
||||
}
|
||||
|
||||
function saveLocalVal(funId, key, val) {
|
||||
const wallet = !window.jc || !jc.wallet ? new jcwallet.default({ type: 0 }) : jc.wallet;
|
||||
wallet.saveLocalItem(key, val);
|
||||
return JSON.stringify({ errcode: 0, data: 1 });
|
||||
}
|
||||
|
||||
function loadLocalVal(funId, key) {
|
||||
const wallet = !window.jc || !jc.wallet ? new jcwallet.default({ type: 0 }) : jc.wallet;
|
||||
let val = wallet.loadLocalItem(key);
|
||||
return JSON.stringify({ errcode: 0, data: val });
|
||||
}
|
||||
|
@ -46,7 +46,7 @@
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:theme="@style/CEBGThemeSelector"
|
||||
android:theme="@style/CebgMainActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density"
|
||||
android:resizeableActivity="false"
|
||||
@ -103,6 +103,9 @@
|
||||
<activity
|
||||
android:name=".activity.WebPageActivity"
|
||||
android:theme="@style/WebViewTheme" />
|
||||
<activity
|
||||
android:name=".activity.BiometricActivity"
|
||||
android:theme="@style/AppCompatTranslucent" />
|
||||
<activity
|
||||
android:name=".activity.CustomCaptureActivity"
|
||||
android:theme="@style/CaptureTheme" />
|
||||
@ -227,7 +230,7 @@
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29" />
|
||||
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<queries>
|
||||
<package android:name="com.zhiliaoapp.musically" />
|
||||
<package android:name="com.ss.android.ugc.trill" />
|
||||
|
@ -1,5 +1,8 @@
|
||||
package com.cege.games.release;
|
||||
|
||||
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
||||
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -7,10 +10,10 @@ import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.view.Window;
|
||||
import android.widget.Toast;
|
||||
@ -19,6 +22,8 @@ import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.biometric.BiometricManager;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
|
||||
@ -28,7 +33,9 @@ import com.bytedance.sdk.open.tiktok.authorize.model.Authorization;
|
||||
import com.bytedance.sdk.open.tiktok.base.MediaContent;
|
||||
import com.bytedance.sdk.open.tiktok.base.VideoObject;
|
||||
import com.bytedance.sdk.open.tiktok.share.Share;
|
||||
import com.cege.games.release.activity.BiometricActivity;
|
||||
import com.cege.games.release.activity.CustomCaptureActivity;
|
||||
import com.cege.games.release.activity.UnityPlayerActivity;
|
||||
import com.cege.games.release.activity.WebPageActivity;
|
||||
import com.cege.games.release.apple.AppleLoginActivity;
|
||||
import com.cege.games.release.dialog.QRCodeActivity;
|
||||
@ -56,12 +63,14 @@ import com.jc.jcfw.JcSDK;
|
||||
import com.jc.jcfw.appauth.AuthStateManager;
|
||||
import com.jc.jcfw.appauth.JConfiguration;
|
||||
import com.jc.jcfw.google.PayClient;
|
||||
import com.jc.jcfw.util.FileUtils;
|
||||
import com.jc.jcfw.security.BiometricHelper;
|
||||
import com.jc.jcfw.security.CryptographyManager;
|
||||
import com.jc.jcfw.security.CryptographyManagerImpl;
|
||||
import com.jc.jcfw.security.EncryptedData;
|
||||
import com.jc.jcfw.util.JsonUtils;
|
||||
import com.king.zxing.CameraScan;
|
||||
import com.king.zxing.util.CodeUtils;
|
||||
import com.king.zxing.util.LogUtils;
|
||||
import com.unity3d.player.UnityPlayerActivity;
|
||||
|
||||
import net.openid.appauth.AppAuthConfiguration;
|
||||
import net.openid.appauth.AuthState;
|
||||
@ -93,6 +102,8 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import pub.devrel.easypermissions.AfterPermissionGranted;
|
||||
import pub.devrel.easypermissions.EasyPermissions;
|
||||
|
||||
@ -136,7 +147,6 @@ public class MainActivity extends UnityPlayerActivity
|
||||
private CountDownLatch mAuthIntentLatch = new CountDownLatch(1);
|
||||
|
||||
private TikTokOpenApi tiktokOpenApi;
|
||||
|
||||
public String getFunId() {
|
||||
return funId;
|
||||
}
|
||||
@ -150,6 +160,8 @@ public class MainActivity extends UnityPlayerActivity
|
||||
|
||||
private FirebaseAnalytics mFirebaseAnalytics;
|
||||
private AppEventsLogger fbLogger;
|
||||
private BiometricPrompt.PromptInfo promptInfo;
|
||||
private CryptographyManager cryptographyManager;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -192,6 +204,8 @@ public class MainActivity extends UnityPlayerActivity
|
||||
|
||||
PayClient payClient = PayClient.getInstance();
|
||||
payClient.init(this);
|
||||
promptInfo = BiometricHelper.createPromptInfo(this);
|
||||
cryptographyManager = new CryptographyManagerImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -270,17 +284,7 @@ public class MainActivity extends UnityPlayerActivity
|
||||
}
|
||||
|
||||
// begin for unity
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
// To support deep linking, we need to make sure that the client can get access
|
||||
// to
|
||||
// the last sent intent. The clients access this through a JNI api that allows
|
||||
// them
|
||||
// to get the intent set on launch. To update that after launch we have to
|
||||
// manually
|
||||
// replace the intent with the one caught here.
|
||||
setIntent(intent);
|
||||
}
|
||||
|
||||
|
||||
protected void onLoadNativeLibraries() {
|
||||
try {
|
||||
@ -851,11 +855,15 @@ public class MainActivity extends UnityPlayerActivity
|
||||
public void showPage(String fid, final String url) {
|
||||
runOnUiThread(() -> {
|
||||
Log.i(TAG, "show page: " + url);
|
||||
Intent intent = new Intent(this, WebPageActivity.class);
|
||||
intent.putExtra("url", url);
|
||||
startActivity(intent);
|
||||
// Intent intent = new Intent(this, WebPageActivity.class);
|
||||
// intent.putExtra("url", url);
|
||||
// Intent intent = new Intent(this, BiometricActivity.class);
|
||||
// intent.putExtra("action", "encrypt");
|
||||
// intent.putExtra("key", "1111");
|
||||
// startActivity(intent);
|
||||
// picker video file and share to tiktok
|
||||
// openFileSelector();
|
||||
authenticateToEncrypt("1111");
|
||||
});
|
||||
}
|
||||
|
||||
@ -902,4 +910,32 @@ public class MainActivity extends UnityPlayerActivity
|
||||
startActivityForResult(intent, FILE_SELECTOR_CODE);
|
||||
}
|
||||
|
||||
public void authenticateToEncrypt(String text) {
|
||||
if (BiometricManager.from(this).canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager
|
||||
.BIOMETRIC_SUCCESS) {
|
||||
Cipher cipher = cryptographyManager.getInitializedCipherForEncryption("cebg_wallet_key");
|
||||
BiometricPrompt biometricPrompt = BiometricHelper.createBiometricPrompt(MainActivity.this, _cipher -> {
|
||||
EncryptedData encryptedData = cryptographyManager.encryptData(text, _cipher);
|
||||
String encryptedString = Base64.encodeToString(encryptedData.getCiphertext(), Base64.DEFAULT);
|
||||
String ivString = Base64.encodeToString(encryptedData.getInitializationVector(), Base64.DEFAULT);
|
||||
Log.i(TAG, "encrypted msg: " + encryptedString);
|
||||
Log.i(TAG, "encrypted iv: " + ivString);
|
||||
});
|
||||
biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher));
|
||||
}
|
||||
}
|
||||
public void authenticateToDecrypt(String text, String iv) {
|
||||
if (BiometricManager.from(this).canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager
|
||||
.BIOMETRIC_SUCCESS) {
|
||||
byte[] ivData = Base64.decode(iv, Base64.DEFAULT);
|
||||
byte[] textData = Base64.decode(text, Base64.DEFAULT);
|
||||
Cipher cipher = cryptographyManager.getInitializedCipherForDecryption("cebg_wallet_key", ivData);
|
||||
BiometricPrompt biometricPrompt = BiometricHelper.createBiometricPrompt(MainActivity.this, _cipher -> {
|
||||
String decryptedMsg = cryptographyManager.decryptData(textData, _cipher);
|
||||
Log.i(TAG, "decrypted msg: " + decryptedMsg);
|
||||
});
|
||||
biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.cege.games.release.activity;
|
||||
|
||||
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
||||
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.biometric.BiometricManager;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
|
||||
import com.cege.games.release.R;
|
||||
import com.jc.jcfw.security.BiometricHelper;
|
||||
import com.jc.jcfw.security.CryptographyManager;
|
||||
import com.jc.jcfw.security.CryptographyManagerImpl;
|
||||
import com.jc.jcfw.security.EncryptedData;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class BiometricActivity extends AppCompatActivity {
|
||||
private static final String TAG = BiometricActivity.class.getSimpleName();
|
||||
private BiometricPrompt.PromptInfo promptInfo;
|
||||
private CryptographyManager cryptographyManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_biometric);
|
||||
Log.i(TAG, "onCreate: " + getIntent().getDataString());
|
||||
promptInfo = BiometricHelper.createPromptInfo(this);
|
||||
cryptographyManager = new CryptographyManagerImpl();
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getStringExtra("action");
|
||||
String key = intent.getStringExtra("key");
|
||||
// check if action is exists
|
||||
// if ("encrypt".equals(action)) {
|
||||
// authenticateToEncrypt(key);
|
||||
// } else if ("decrypt".equals(action)) {
|
||||
// String iv = intent.getStringExtra("iv");
|
||||
// authenticateToDecrypt(key, iv);
|
||||
// }
|
||||
}
|
||||
|
||||
public void authenticateToEncrypt(String text) {
|
||||
if (BiometricManager.from(this)
|
||||
.canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS) {
|
||||
Cipher cipher = cryptographyManager.getInitializedCipherForEncryption("cebg_wallet_key");
|
||||
BiometricPrompt biometricPrompt = BiometricHelper.createBiometricPrompt(this, _cipher -> {
|
||||
EncryptedData encryptedData = cryptographyManager.encryptData(text, _cipher);
|
||||
String encryptedString = Base64.encodeToString(encryptedData.getCiphertext(), Base64.DEFAULT);
|
||||
String ivString = Base64.encodeToString(encryptedData.getInitializationVector(), Base64.DEFAULT);
|
||||
Log.i(TAG, "encrypted msg: " + encryptedString);
|
||||
Log.i(TAG, "encrypted iv: " + ivString);
|
||||
});
|
||||
biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher));
|
||||
}
|
||||
}
|
||||
|
||||
public void authenticateToDecrypt(String text, String iv) {
|
||||
if (BiometricManager.from(this)
|
||||
.canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) == BiometricManager.BIOMETRIC_SUCCESS) {
|
||||
byte[] ivData = Base64.decode(iv, Base64.DEFAULT);
|
||||
byte[] textData = Base64.decode(text, Base64.DEFAULT);
|
||||
Cipher cipher = cryptographyManager.getInitializedCipherForDecryption("cebg_wallet_key", ivData);
|
||||
BiometricPrompt biometricPrompt = BiometricHelper.createBiometricPrompt(this, _cipher -> {
|
||||
String decryptedMsg = cryptographyManager.decryptData(textData, _cipher);
|
||||
Log.i(TAG, "decrypted msg: " + decryptedMsg);
|
||||
});
|
||||
biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
168
app/src/com/cege/games/release/activity/UnityPlayerActivity.java
Normal file
168
app/src/com/cege/games/release/activity/UnityPlayerActivity.java
Normal file
@ -0,0 +1,168 @@
|
||||
package com.cege.games.release.activity;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Window;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.unity3d.player.IUnityPlayerLifecycleEvents;
|
||||
import com.unity3d.player.MultiWindowSupport;
|
||||
import com.unity3d.player.UnityPlayer;
|
||||
|
||||
public class UnityPlayerActivity extends AppCompatActivity implements IUnityPlayerLifecycleEvents
|
||||
{
|
||||
protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
|
||||
|
||||
// Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player
|
||||
// The command line arguments are passed as a string, separated by spaces
|
||||
// UnityPlayerActivity calls this from 'onCreate'
|
||||
// Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan
|
||||
// See https://docs.unity3d.com/Manual/CommandLineArguments.html
|
||||
// @param cmdLine the current command line arguments, may be null
|
||||
// @return the modified command line string or null
|
||||
protected String updateUnityCommandLineArguments(String cmdLine)
|
||||
{
|
||||
return cmdLine;
|
||||
}
|
||||
|
||||
// Setup activity layout
|
||||
@Override protected void onCreate(Bundle savedInstanceState)
|
||||
{
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity"));
|
||||
getIntent().putExtra("unity", cmdLine);
|
||||
|
||||
mUnityPlayer = new UnityPlayer(this, this);
|
||||
setContentView(mUnityPlayer);
|
||||
mUnityPlayer.requestFocus();
|
||||
}
|
||||
|
||||
// When Unity player unloaded move task to background
|
||||
@Override public void onUnityPlayerUnloaded() {
|
||||
moveTaskToBack(true);
|
||||
}
|
||||
|
||||
// Callback before Unity player process is killed
|
||||
@Override public void onUnityPlayerQuitted() {
|
||||
}
|
||||
|
||||
@Override protected void onNewIntent(Intent intent) {
|
||||
// To support deep linking, we need to make sure that the client can get access to
|
||||
// the last sent intent. The clients access this through a JNI api that allows them
|
||||
// to get the intent set on launch. To update that after launch we have to manually
|
||||
// replace the intent with the one caught here.
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
mUnityPlayer.newIntent(intent);
|
||||
}
|
||||
|
||||
// Quit Unity
|
||||
@Override protected void onDestroy ()
|
||||
{
|
||||
mUnityPlayer.destroy();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
// If the activity is in multi window mode or resizing the activity is allowed we will use
|
||||
// onStart/onStop (the visibility callbacks) to determine when to pause/resume.
|
||||
// Otherwise it will be done in onPause/onResume as Unity has done historically to preserve
|
||||
// existing behavior.
|
||||
@Override protected void onStop()
|
||||
{
|
||||
super.onStop();
|
||||
|
||||
if (!MultiWindowSupport.getAllowResizableWindow(this))
|
||||
return;
|
||||
|
||||
mUnityPlayer.pause();
|
||||
}
|
||||
|
||||
@Override protected void onStart()
|
||||
{
|
||||
super.onStart();
|
||||
|
||||
if (!MultiWindowSupport.getAllowResizableWindow(this))
|
||||
return;
|
||||
|
||||
mUnityPlayer.resume();
|
||||
}
|
||||
|
||||
// Pause Unity
|
||||
@Override protected void onPause()
|
||||
{
|
||||
super.onPause();
|
||||
|
||||
MultiWindowSupport.saveMultiWindowMode(this);
|
||||
|
||||
if (MultiWindowSupport.getAllowResizableWindow(this))
|
||||
return;
|
||||
|
||||
mUnityPlayer.pause();
|
||||
}
|
||||
|
||||
// Resume Unity
|
||||
@Override protected void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
|
||||
if (MultiWindowSupport.getAllowResizableWindow(this) && !MultiWindowSupport.isMultiWindowModeChangedToTrue(this))
|
||||
return;
|
||||
|
||||
mUnityPlayer.resume();
|
||||
}
|
||||
|
||||
// Low Memory Unity
|
||||
@Override public void onLowMemory()
|
||||
{
|
||||
super.onLowMemory();
|
||||
mUnityPlayer.lowMemory();
|
||||
}
|
||||
|
||||
// Trim Memory Unity
|
||||
@Override public void onTrimMemory(int level)
|
||||
{
|
||||
super.onTrimMemory(level);
|
||||
if (level == TRIM_MEMORY_RUNNING_CRITICAL)
|
||||
{
|
||||
mUnityPlayer.lowMemory();
|
||||
}
|
||||
}
|
||||
|
||||
// This ensures the layout will be correct.
|
||||
@Override public void onConfigurationChanged(Configuration newConfig)
|
||||
{
|
||||
super.onConfigurationChanged(newConfig);
|
||||
mUnityPlayer.configurationChanged(newConfig);
|
||||
}
|
||||
|
||||
// Notify Unity of the focus change.
|
||||
@Override public void onWindowFocusChanged(boolean hasFocus)
|
||||
{
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
mUnityPlayer.windowFocusChanged(hasFocus);
|
||||
}
|
||||
|
||||
// For some reason the multiple keyevent type is not supported by the ndk.
|
||||
// Force event injection by overriding dispatchKeyEvent().
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override public boolean dispatchKeyEvent(KeyEvent event)
|
||||
{
|
||||
if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
|
||||
return mUnityPlayer.injectEvent(event);
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
// Pass any events not handled by (unfocused) views straight to UnityPlayer
|
||||
@Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); }
|
||||
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mUnityPlayer.injectEvent(event); }
|
||||
@Override public boolean onTouchEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }
|
||||
/*API12*/ public boolean onGenericMotionEvent(MotionEvent event) { return mUnityPlayer.injectEvent(event); }
|
||||
}
|
62
app/src/com/jc/jcfw/security/BiometricHelper.java
Normal file
62
app/src/com/jc/jcfw/security/BiometricHelper.java
Normal file
@ -0,0 +1,62 @@
|
||||
package com.jc.jcfw.security;
|
||||
|
||||
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG;
|
||||
import static androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.cege.games.release.R;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class BiometricHelper {
|
||||
private static final String TAG = BiometricHelper.class.getSimpleName();
|
||||
public static BiometricPrompt.PromptInfo createPromptInfo(Context context) {
|
||||
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(context.getString(R.string.prompt_info_title)) // e.g. "Sign in"
|
||||
.setSubtitle(context.getString(R.string.prompt_info_subtitle)) // e.g. "Biometric for My App"
|
||||
.setDescription(context.getString(R.string.prompt_info_description)) // e.g. "Confirm biometric to continue"
|
||||
.setConfirmationRequired(false)
|
||||
.setAllowedAuthenticators(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)
|
||||
// .setDeviceCredentialAllowed(true) // Allow PIN/pattern/password authentication.
|
||||
// Also note that setDeviceCredentialAllowed and setNegativeButtonText are
|
||||
// incompatible so that if you uncomment one you must comment out the other
|
||||
.build();
|
||||
return promptInfo;
|
||||
}
|
||||
|
||||
public static BiometricPrompt createBiometricPrompt(FragmentActivity activity, Consumer<Cipher> func) {
|
||||
Executor executor = ContextCompat.getMainExecutor(activity.getApplicationContext());
|
||||
return new BiometricPrompt(activity, executor, new BiometricPrompt.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
||||
super.onAuthenticationError(errorCode, errString);
|
||||
Log.i(TAG, "Authentication error: " + errString);
|
||||
}
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed();
|
||||
Log.i(TAG, "Authentication failed!");
|
||||
}
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
Log.i(TAG, "Authentication succeeded!");
|
||||
try {
|
||||
func.accept(result.getCryptoObject().getCipher());
|
||||
} catch (Exception e) {
|
||||
Log.e("BiometricHelper", e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
27
app/src/com/jc/jcfw/security/CryptographyManager.java
Normal file
27
app/src/com/jc/jcfw/security/CryptographyManager.java
Normal file
@ -0,0 +1,27 @@
|
||||
package com.jc.jcfw.security;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public interface CryptographyManager {
|
||||
/**
|
||||
* This method first gets or generates an instance of SecretKey and then initializes the Cipher
|
||||
* with the key. The secret key uses [ENCRYPT_MODE][Cipher.ENCRYPT_MODE] is used.
|
||||
*/
|
||||
Cipher getInitializedCipherForEncryption(String keyName);
|
||||
|
||||
/**
|
||||
* This method first gets or generates an instance of SecretKey and then initializes the Cipher
|
||||
* with the key. The secret key uses [DECRYPT_MODE][Cipher.DECRYPT_MODE] is used.
|
||||
*/
|
||||
Cipher getInitializedCipherForDecryption(String keyName, byte[] initializationVector);
|
||||
|
||||
/**
|
||||
* The Cipher created with [getInitializedCipherForEncryption] is used here
|
||||
*/
|
||||
EncryptedData encryptData(String plaintext, Cipher cipher);
|
||||
|
||||
/**
|
||||
* The Cipher created with [getInitializedCipherForDecryption] is used here
|
||||
*/
|
||||
String decryptData(byte[] ciphertext, Cipher cipher);
|
||||
}
|
97
app/src/com/jc/jcfw/security/CryptographyManagerImpl.java
Normal file
97
app/src/com/jc/jcfw/security/CryptographyManagerImpl.java
Normal file
@ -0,0 +1,97 @@
|
||||
package com.jc.jcfw.security;
|
||||
|
||||
import static android.security.keystore.KeyProperties.BLOCK_MODE_CBC;
|
||||
import static android.security.keystore.KeyProperties.ENCRYPTION_PADDING_PKCS7;
|
||||
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyProperties;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
public class CryptographyManagerImpl implements CryptographyManager {
|
||||
|
||||
@Override
|
||||
public Cipher getInitializedCipherForEncryption(String keyName) {
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = getCipher();
|
||||
SecretKey secretKey = getOrCreateSecretKey(keyName);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to initialize Cipher for encryption", e);
|
||||
}
|
||||
return cipher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cipher getInitializedCipherForDecryption(String keyName, byte[] initializationVector) {
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = getCipher();
|
||||
SecretKey secretKey = getOrCreateSecretKey(keyName);
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(initializationVector));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to initialize Cipher for decryption", e);
|
||||
}
|
||||
return cipher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncryptedData encryptData(String plaintext, Cipher cipher) {
|
||||
try {
|
||||
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(Charset.defaultCharset()));
|
||||
return new EncryptedData(ciphertext, cipher.getIV());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to encrypt data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decryptData(byte[] ciphertext, Cipher cipher) {
|
||||
try {
|
||||
byte[] plaintext = cipher.doFinal(ciphertext);
|
||||
return new String(plaintext, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to encrypt data", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Cipher getCipher() throws Exception {
|
||||
return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||
+ BLOCK_MODE_CBC + "/"
|
||||
+ ENCRYPTION_PADDING_PKCS7);
|
||||
}
|
||||
|
||||
private SecretKey getOrCreateSecretKey(String keyName) throws Exception {
|
||||
String ANDROID_KEYSTORE = "AndroidKeyStore";
|
||||
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
|
||||
keyStore.load(null); // Keystore must be loaded before it can be accessed
|
||||
SecretKey existingKey = (SecretKey) keyStore.getKey(keyName, null);
|
||||
if (existingKey != null) {
|
||||
return existingKey;
|
||||
}
|
||||
|
||||
// if you reach here, then a new SecretKey must be generated for that keyName
|
||||
KeyGenParameterSpec.Builder paramsBuilder = new KeyGenParameterSpec.Builder(keyName,
|
||||
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT);
|
||||
paramsBuilder.setBlockModes(BLOCK_MODE_CBC);
|
||||
paramsBuilder.setEncryptionPaddings(ENCRYPTION_PADDING_PKCS7);
|
||||
int KEY_SIZE = 256;
|
||||
paramsBuilder.setKeySize(KEY_SIZE);
|
||||
paramsBuilder.setUserAuthenticationRequired(false);
|
||||
paramsBuilder.setUserAuthenticationValidityDurationSeconds(30);
|
||||
|
||||
KeyGenParameterSpec keyGenParams = paramsBuilder.build();
|
||||
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,
|
||||
ANDROID_KEYSTORE);
|
||||
keyGenerator.init(keyGenParams);
|
||||
return keyGenerator.generateKey();
|
||||
}
|
||||
}
|
19
app/src/com/jc/jcfw/security/EncryptedData.java
Normal file
19
app/src/com/jc/jcfw/security/EncryptedData.java
Normal file
@ -0,0 +1,19 @@
|
||||
package com.jc.jcfw.security;
|
||||
|
||||
public class EncryptedData {
|
||||
private final byte[] ciphertext;
|
||||
private final byte[] initializationVector;
|
||||
|
||||
public EncryptedData(byte[] ciphertext, byte[] initializationVector) {
|
||||
this.ciphertext = ciphertext;
|
||||
this.initializationVector = initializationVector;
|
||||
}
|
||||
|
||||
public byte[] getCiphertext() {
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
public byte[] getInitializationVector() {
|
||||
return initializationVector;
|
||||
}
|
||||
}
|
8
app/src/main/res/layout/activity_biometric.xml
Normal file
8
app/src/main/res/layout/activity_biometric.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?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="horizontal">
|
||||
|
||||
|
||||
</LinearLayout>
|
@ -9,4 +9,8 @@
|
||||
<string name="facebook_app_id" translatable="false">1204701000119770</string>
|
||||
<string name="fb_login_protocol_scheme" translatable="false">fb1204701000119770</string>
|
||||
<string name="facebook_client_token" translatable="false">3e29f2606ae15a99bb3824d2ef1a9d0b</string>
|
||||
|
||||
<string name="prompt_info_title" translatable="false">Biometric login for Wallet</string>
|
||||
<string name="prompt_info_subtitle" translatable="false">Login using your biometric credential</string>
|
||||
<string name="prompt_info_description" translatable="false">Confirm biometric to continue</string>
|
||||
</resources>
|
@ -6,6 +6,21 @@
|
||||
</style>
|
||||
<style name="BaseUnityTheme" parent="android:Theme.Holo.Light.NoActionBar.Fullscreen">
|
||||
</style>
|
||||
<style name="AppCompatTranslucent" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
|
||||
</style>
|
||||
<style name="CebgMainActivity" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
</style>
|
||||
<style name="WebViewTheme" parent="android:Theme.Holo.Light.NoActionBar.Fullscreen">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="colorPrimary">@android:color/black</item>
|
||||
|
Loading…
x
Reference in New Issue
Block a user