移除java代码
This commit is contained in:
parent
ed0f5f42da
commit
17cfd36705
@ -219,16 +219,6 @@
|
|||||||
<category android:name="android.intent.category.default" />
|
<category android:name="android.intent.category.default" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<service
|
|
||||||
android:name="com.jc.jcfw.accountmanager.AuthenticatorService"
|
|
||||||
>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.accounts.AccountAuthenticator" />
|
|
||||||
</intent-filter>
|
|
||||||
<meta-data android:name="android.accounts.AccountAuthenticator"
|
|
||||||
android:resource="@xml/authenticator" />
|
|
||||||
</service>
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
<supports-screens
|
<supports-screens
|
||||||
android:anyDensity="true"
|
android:anyDensity="true"
|
||||||
|
@ -52,14 +52,14 @@ class BiometricActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
val encryptedData = cryptographyManager!!.encryptData(text, _result.cipher)
|
val encryptedData = cryptographyManager!!.encryptData(text, _result.cipher)
|
||||||
val encryptedString =
|
val encryptedString =
|
||||||
Base64.encodeToString(encryptedData.ciphertext, Base64.DEFAULT)
|
Base64.encodeToString(encryptedData!!.ciphertext, Base64.DEFAULT)
|
||||||
val ivString =
|
val ivString =
|
||||||
Base64.encodeToString(encryptedData.initializationVector, Base64.DEFAULT)
|
Base64.encodeToString(encryptedData.initializationVector, Base64.DEFAULT)
|
||||||
Log.i(TAG, "encrypted msg: $encryptedString")
|
Log.i(TAG, "encrypted msg: $encryptedString")
|
||||||
Log.i(TAG, "encrypted iv: $ivString")
|
Log.i(TAG, "encrypted iv: $ivString")
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
biometricPrompt.authenticate(promptInfo!!, BiometricPrompt.CryptoObject(cipher))
|
biometricPrompt.authenticate(promptInfo!!, BiometricPrompt.CryptoObject(cipher!!))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ class BiometricActivity : AppCompatActivity() {
|
|||||||
Log.i(TAG, "decrypted msg: $decryptedMsg")
|
Log.i(TAG, "decrypted msg: $decryptedMsg")
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
biometricPrompt.authenticate(promptInfo!!, BiometricPrompt.CryptoObject(cipher))
|
biometricPrompt.authenticate(promptInfo!!, BiometricPrompt.CryptoObject(cipher!!))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
package com.jc.jcfw.accountmanager;
|
|
||||||
|
|
||||||
import android.accounts.AbstractAccountAuthenticator;
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.accounts.AccountAuthenticatorResponse;
|
|
||||||
import android.accounts.NetworkErrorException;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by sebastian on 07-03-16.
|
|
||||||
*/
|
|
||||||
public class Authenticator extends AbstractAccountAuthenticator {
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
public Authenticator(Context context) {
|
|
||||||
super(context);
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAuthTokenLabel(String authTokenType) {
|
|
||||||
return authTokenType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package com.jc.jcfw.accountmanager;
|
|
||||||
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.IBinder;
|
|
||||||
|
|
||||||
public class AuthenticatorService extends Service {
|
|
||||||
private Authenticator authenticator;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
authenticator = new Authenticator(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return authenticator.getIBinder();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,150 +0,0 @@
|
|||||||
package com.jc.jcfw.appauth;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import net.openid.appauth.AuthState;
|
|
||||||
import net.openid.appauth.AuthorizationException;
|
|
||||||
import net.openid.appauth.AuthorizationResponse;
|
|
||||||
import net.openid.appauth.RegistrationResponse;
|
|
||||||
import net.openid.appauth.TokenResponse;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
import androidx.annotation.AnyThread;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
public class AuthStateManager {
|
|
||||||
|
|
||||||
private static final AtomicReference<WeakReference<AuthStateManager>> INSTANCE_REF =
|
|
||||||
new AtomicReference<>(new WeakReference<>(null));
|
|
||||||
|
|
||||||
private static final String TAG = "AuthStateManager";
|
|
||||||
|
|
||||||
private static final String STORE_NAME = "AuthState";
|
|
||||||
private static final String KEY_STATE = "state";
|
|
||||||
|
|
||||||
private final SharedPreferences mPrefs;
|
|
||||||
private final ReentrantLock mPrefsLock;
|
|
||||||
private final AtomicReference<AuthState> mCurrentAuthState;
|
|
||||||
|
|
||||||
@AnyThread
|
|
||||||
public static AuthStateManager getInstance(@NonNull Context context) {
|
|
||||||
AuthStateManager manager = INSTANCE_REF.get().get();
|
|
||||||
if (manager == null) {
|
|
||||||
manager = new AuthStateManager(context.getApplicationContext());
|
|
||||||
INSTANCE_REF.set(new WeakReference<>(manager));
|
|
||||||
}
|
|
||||||
|
|
||||||
return manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AuthStateManager(Context context) {
|
|
||||||
mPrefs = context.getSharedPreferences(STORE_NAME, Context.MODE_PRIVATE);
|
|
||||||
mPrefsLock = new ReentrantLock();
|
|
||||||
mCurrentAuthState = new AtomicReference<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AnyThread
|
|
||||||
@NonNull
|
|
||||||
public AuthState getCurrent() {
|
|
||||||
if (mCurrentAuthState.get() != null) {
|
|
||||||
return mCurrentAuthState.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthState state = readState();
|
|
||||||
if (mCurrentAuthState.compareAndSet(null, state)) {
|
|
||||||
return state;
|
|
||||||
} else {
|
|
||||||
return mCurrentAuthState.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AnyThread
|
|
||||||
@NonNull
|
|
||||||
public AuthState replace(@NonNull AuthState state) {
|
|
||||||
writeState(state);
|
|
||||||
mCurrentAuthState.set(state);
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
@AnyThread
|
|
||||||
@NonNull
|
|
||||||
public AuthState updateAfterAuthorization(
|
|
||||||
@Nullable AuthorizationResponse response,
|
|
||||||
@Nullable AuthorizationException ex) {
|
|
||||||
AuthState current = getCurrent();
|
|
||||||
current.update(response, ex);
|
|
||||||
return replace(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AnyThread
|
|
||||||
@NonNull
|
|
||||||
public AuthState updateAfterTokenResponse(
|
|
||||||
@Nullable TokenResponse response,
|
|
||||||
@Nullable AuthorizationException ex) {
|
|
||||||
AuthState current = getCurrent();
|
|
||||||
current.update(response, ex);
|
|
||||||
return replace(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AnyThread
|
|
||||||
@NonNull
|
|
||||||
public AuthState updateAfterRegistration(
|
|
||||||
RegistrationResponse response,
|
|
||||||
AuthorizationException ex) {
|
|
||||||
AuthState current = getCurrent();
|
|
||||||
if (ex != null) {
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
current.update(response);
|
|
||||||
return replace(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AnyThread
|
|
||||||
@NonNull
|
|
||||||
private AuthState readState() {
|
|
||||||
mPrefsLock.lock();
|
|
||||||
try {
|
|
||||||
String currentState = mPrefs.getString(KEY_STATE, null);
|
|
||||||
if (currentState == null) {
|
|
||||||
return new AuthState();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return AuthState.jsonDeserialize(currentState);
|
|
||||||
} catch (JSONException ex) {
|
|
||||||
Log.w(TAG, "Failed to deserialize stored auth state - discarding");
|
|
||||||
return new AuthState();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
mPrefsLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@AnyThread
|
|
||||||
private void writeState(@Nullable AuthState state) {
|
|
||||||
mPrefsLock.lock();
|
|
||||||
try {
|
|
||||||
SharedPreferences.Editor editor = mPrefs.edit();
|
|
||||||
if (state == null) {
|
|
||||||
editor.remove(KEY_STATE);
|
|
||||||
} else {
|
|
||||||
editor.putString(KEY_STATE, state.jsonSerializeString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!editor.commit()) {
|
|
||||||
throw new IllegalStateException("Failed to write state to shared prefs");
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
mPrefsLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
130
app/src/com/jc/jcfw/appauth/AuthStateManager.kt
Normal file
130
app/src/com/jc/jcfw/appauth/AuthStateManager.kt
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package com.jc.jcfw.appauth
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.AnyThread
|
||||||
|
import net.openid.appauth.AuthState
|
||||||
|
import net.openid.appauth.AuthorizationException
|
||||||
|
import net.openid.appauth.AuthorizationResponse
|
||||||
|
import net.openid.appauth.RegistrationResponse
|
||||||
|
import net.openid.appauth.TokenResponse
|
||||||
|
import org.json.JSONException
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
|
||||||
|
class AuthStateManager private constructor(context: Context) {
|
||||||
|
private val mPrefs: SharedPreferences
|
||||||
|
private val mPrefsLock: ReentrantLock
|
||||||
|
private val mCurrentAuthState: AtomicReference<AuthState>
|
||||||
|
|
||||||
|
init {
|
||||||
|
mPrefs = context.getSharedPreferences(STORE_NAME, Context.MODE_PRIVATE)
|
||||||
|
mPrefsLock = ReentrantLock()
|
||||||
|
mCurrentAuthState = AtomicReference()
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:AnyThread
|
||||||
|
val current: AuthState
|
||||||
|
get() {
|
||||||
|
if (mCurrentAuthState.get() != null) {
|
||||||
|
return mCurrentAuthState.get()
|
||||||
|
}
|
||||||
|
val state = readState()
|
||||||
|
return if (mCurrentAuthState.compareAndSet(null, state)) {
|
||||||
|
state
|
||||||
|
} else {
|
||||||
|
mCurrentAuthState.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun replace(state: AuthState): AuthState {
|
||||||
|
writeState(state)
|
||||||
|
mCurrentAuthState.set(state)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun updateAfterAuthorization(
|
||||||
|
response: AuthorizationResponse?,
|
||||||
|
ex: AuthorizationException?
|
||||||
|
): AuthState {
|
||||||
|
val current = current
|
||||||
|
current.update(response, ex)
|
||||||
|
return replace(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun updateAfterTokenResponse(
|
||||||
|
response: TokenResponse?,
|
||||||
|
ex: AuthorizationException?
|
||||||
|
): AuthState {
|
||||||
|
val current = current
|
||||||
|
current.update(response, ex)
|
||||||
|
return replace(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
fun updateAfterRegistration(
|
||||||
|
response: RegistrationResponse?,
|
||||||
|
ex: AuthorizationException?
|
||||||
|
): AuthState {
|
||||||
|
val current = current
|
||||||
|
if (ex != null) {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
current.update(response)
|
||||||
|
return replace(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
private fun readState(): AuthState {
|
||||||
|
mPrefsLock.lock()
|
||||||
|
return try {
|
||||||
|
val currentState = mPrefs.getString(KEY_STATE, null)
|
||||||
|
?: return AuthState()
|
||||||
|
try {
|
||||||
|
AuthState.jsonDeserialize(currentState)
|
||||||
|
} catch (ex: JSONException) {
|
||||||
|
Log.w(TAG, "Failed to deserialize stored auth state - discarding")
|
||||||
|
AuthState()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
mPrefsLock.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AnyThread
|
||||||
|
private fun writeState(state: AuthState?) {
|
||||||
|
mPrefsLock.lock()
|
||||||
|
try {
|
||||||
|
val editor = mPrefs.edit()
|
||||||
|
if (state == null) {
|
||||||
|
editor.remove(KEY_STATE)
|
||||||
|
} else {
|
||||||
|
editor.putString(KEY_STATE, state.jsonSerializeString())
|
||||||
|
}
|
||||||
|
check(editor.commit()) { "Failed to write state to shared prefs" }
|
||||||
|
} finally {
|
||||||
|
mPrefsLock.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val INSTANCE_REF = AtomicReference(WeakReference<AuthStateManager?>(null))
|
||||||
|
private const val TAG = "AuthStateManager"
|
||||||
|
private const val STORE_NAME = "AuthState"
|
||||||
|
private const val KEY_STATE = "state"
|
||||||
|
@AnyThread
|
||||||
|
fun getInstance(context: Context): AuthStateManager {
|
||||||
|
var manager = INSTANCE_REF.get().get()
|
||||||
|
if (manager == null) {
|
||||||
|
manager = AuthStateManager(context.applicationContext)
|
||||||
|
INSTANCE_REF.set(WeakReference(manager))
|
||||||
|
}
|
||||||
|
return manager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,108 +0,0 @@
|
|||||||
package com.jc.jcfw.appauth;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import net.openid.appauth.Preconditions;
|
|
||||||
import net.openid.appauth.connectivity.ConnectionBuilder;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.security.KeyManagementException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.SSLSession;
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
public final class ConnectionBuilderForTesting implements ConnectionBuilder {
|
|
||||||
|
|
||||||
public static final ConnectionBuilderForTesting INSTANCE = new ConnectionBuilderForTesting();
|
|
||||||
|
|
||||||
private static final String TAG = "ConnBuilder";
|
|
||||||
|
|
||||||
private static final int CONNECTION_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(15);
|
|
||||||
private static final int READ_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(10);
|
|
||||||
|
|
||||||
private static final String HTTP = "http";
|
|
||||||
private static final String HTTPS = "https";
|
|
||||||
|
|
||||||
@SuppressLint("TrustAllX509TrustManager")
|
|
||||||
private static final TrustManager[] ANY_CERT_MANAGER = new TrustManager[] {
|
|
||||||
new X509TrustManager() {
|
|
||||||
public X509Certificate[] getAcceptedIssuers() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
|
|
||||||
|
|
||||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@SuppressLint("BadHostnameVerifier")
|
|
||||||
private static final HostnameVerifier ANY_HOSTNAME_VERIFIER = new HostnameVerifier() {
|
|
||||||
public boolean verify(String hostname, SSLSession session) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static final SSLContext TRUSTING_CONTEXT;
|
|
||||||
|
|
||||||
static {
|
|
||||||
SSLContext context;
|
|
||||||
try {
|
|
||||||
context = SSLContext.getInstance("SSL");
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
Log.e("ConnBuilder", "Unable to acquire SSL context");
|
|
||||||
context = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
SSLContext initializedContext = null;
|
|
||||||
if (context != null) {
|
|
||||||
try {
|
|
||||||
context.init(null, ANY_CERT_MANAGER, new java.security.SecureRandom());
|
|
||||||
initializedContext = context;
|
|
||||||
} catch (KeyManagementException e) {
|
|
||||||
Log.e(TAG, "Failed to initialize trusting SSL context");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TRUSTING_CONTEXT = initializedContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConnectionBuilderForTesting() {
|
|
||||||
// no need to construct new instances
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public HttpURLConnection openConnection(@NonNull Uri uri) throws IOException {
|
|
||||||
Preconditions.checkNotNull(uri, "url must not be null");
|
|
||||||
Preconditions.checkArgument(HTTP.equals(uri.getScheme()) || HTTPS.equals(uri.getScheme()),
|
|
||||||
"scheme or uri must be http or https");
|
|
||||||
HttpURLConnection conn = (HttpURLConnection) new URL(uri.toString()).openConnection();
|
|
||||||
conn.setConnectTimeout(CONNECTION_TIMEOUT_MS);
|
|
||||||
conn.setReadTimeout(READ_TIMEOUT_MS);
|
|
||||||
conn.setInstanceFollowRedirects(false);
|
|
||||||
|
|
||||||
if (conn instanceof HttpsURLConnection && TRUSTING_CONTEXT != null) {
|
|
||||||
HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
|
|
||||||
httpsConn.setSSLSocketFactory(TRUSTING_CONTEXT.getSocketFactory());
|
|
||||||
httpsConn.setHostnameVerifier(ANY_HOSTNAME_VERIFIER);
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,310 +0,0 @@
|
|||||||
package com.jc.jcfw.appauth;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import com.ctf.games.release.R;
|
|
||||||
|
|
||||||
import net.openid.appauth.connectivity.ConnectionBuilder;
|
|
||||||
import net.openid.appauth.connectivity.DefaultConnectionBuilder;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import okio.Buffer;
|
|
||||||
import okio.BufferedSource;
|
|
||||||
import okio.Okio;
|
|
||||||
|
|
||||||
public final class JConfiguration {
|
|
||||||
|
|
||||||
private static final String TAG = "Configuration";
|
|
||||||
|
|
||||||
private static final String PREFS_NAME = "config";
|
|
||||||
private static final String KEY_LAST_HASH = "lastHash";
|
|
||||||
|
|
||||||
private static WeakReference<JConfiguration> sInstance = new WeakReference<>(null);
|
|
||||||
|
|
||||||
private final Context mContext;
|
|
||||||
private final SharedPreferences mPrefs;
|
|
||||||
private final Resources mResources;
|
|
||||||
|
|
||||||
private JSONObject mConfigJson;
|
|
||||||
private String mConfigHash;
|
|
||||||
private String mConfigError;
|
|
||||||
|
|
||||||
private String mClientId;
|
|
||||||
private String mScope;
|
|
||||||
private Uri mRedirectUri;
|
|
||||||
private Uri mEndSessionRedirectUri;
|
|
||||||
private Uri mDiscoveryUri;
|
|
||||||
private Uri mAuthEndpointUri;
|
|
||||||
private Uri mTokenEndpointUri;
|
|
||||||
private Uri mEndSessionEndpoint;
|
|
||||||
private Uri mRegistrationEndpointUri;
|
|
||||||
private Uri mUserInfoEndpointUri;
|
|
||||||
private boolean mHttpsRequired;
|
|
||||||
|
|
||||||
public static JConfiguration getInstance(Context context) {
|
|
||||||
JConfiguration config = sInstance.get();
|
|
||||||
if (config == null) {
|
|
||||||
config = new JConfiguration(context);
|
|
||||||
sInstance = new WeakReference<JConfiguration>(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JConfiguration(Context context) {
|
|
||||||
mContext = context;
|
|
||||||
mPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
|
||||||
mResources = context.getResources();
|
|
||||||
|
|
||||||
try {
|
|
||||||
readConfiguration();
|
|
||||||
} catch (InvalidConfigurationException ex) {
|
|
||||||
mConfigError = ex.getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether the configuration has changed from the last known valid state.
|
|
||||||
*/
|
|
||||||
public boolean hasConfigurationChanged() {
|
|
||||||
String lastHash = getLastKnownConfigHash();
|
|
||||||
return !mConfigHash.equals(lastHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether the current configuration is valid.
|
|
||||||
*/
|
|
||||||
public boolean isValid() {
|
|
||||||
return mConfigError == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a description of the configuration error, if the configuration is invalid.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public String getConfigurationError() {
|
|
||||||
return mConfigError;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that the current configuration should be accepted as the "last known valid"
|
|
||||||
* configuration.
|
|
||||||
*/
|
|
||||||
public void acceptConfiguration() {
|
|
||||||
mPrefs.edit().putString(KEY_LAST_HASH, mConfigHash).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getClientId() {
|
|
||||||
return mClientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public String getScope() {
|
|
||||||
return mScope;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public Uri getRedirectUri() {
|
|
||||||
return mRedirectUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Uri getDiscoveryUri() {
|
|
||||||
return mDiscoveryUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Uri getEndSessionRedirectUri() { return mEndSessionRedirectUri; }
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Uri getAuthEndpointUri() {
|
|
||||||
return mAuthEndpointUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Uri getTokenEndpointUri() {
|
|
||||||
return mTokenEndpointUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Uri getEndSessionEndpoint() {
|
|
||||||
return mEndSessionEndpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Uri getRegistrationEndpointUri() {
|
|
||||||
return mRegistrationEndpointUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Uri getUserInfoEndpointUri() {
|
|
||||||
return mUserInfoEndpointUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHttpsRequired() {
|
|
||||||
return mHttpsRequired;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionBuilder getConnectionBuilder() {
|
|
||||||
if (isHttpsRequired()) {
|
|
||||||
return DefaultConnectionBuilder.INSTANCE;
|
|
||||||
}
|
|
||||||
return ConnectionBuilderForTesting.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getLastKnownConfigHash() {
|
|
||||||
return mPrefs.getString(KEY_LAST_HASH, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readConfiguration() throws InvalidConfigurationException {
|
|
||||||
BufferedSource configSource =
|
|
||||||
Okio.buffer(Okio.source(mResources.openRawResource(R.raw.auth_config)));
|
|
||||||
Buffer configData = new Buffer();
|
|
||||||
try {
|
|
||||||
configSource.readAll(configData);
|
|
||||||
mConfigJson = new JSONObject(configData.readString(StandardCharsets.UTF_8));
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new InvalidConfigurationException(
|
|
||||||
"Failed to read configuration: " + ex.getMessage());
|
|
||||||
} catch (JSONException ex) {
|
|
||||||
throw new InvalidConfigurationException(
|
|
||||||
"Unable to parse configuration: " + ex.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
mConfigHash = configData.sha256().base64();
|
|
||||||
mClientId = getConfigString("client_id");
|
|
||||||
mScope = getRequiredConfigString("authorization_scope");
|
|
||||||
mRedirectUri = getRequiredConfigUri("redirect_uri");
|
|
||||||
mEndSessionRedirectUri = getRequiredConfigUri("end_session_redirect_uri");
|
|
||||||
|
|
||||||
if (!isRedirectUriRegistered()) {
|
|
||||||
throw new InvalidConfigurationException(
|
|
||||||
"redirect_uri is not handled by any activity in this app! "
|
|
||||||
+ "Ensure that the appAuthRedirectScheme in your build.gradle file "
|
|
||||||
+ "is correctly configured, or that an appropriate intent filter "
|
|
||||||
+ "exists in your app manifest.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getConfigString("discovery_uri") == null) {
|
|
||||||
mAuthEndpointUri = getRequiredConfigWebUri("authorization_endpoint_uri");
|
|
||||||
|
|
||||||
mTokenEndpointUri = getRequiredConfigWebUri("token_endpoint_uri");
|
|
||||||
mUserInfoEndpointUri = getRequiredConfigWebUri("user_info_endpoint_uri");
|
|
||||||
mEndSessionEndpoint = getRequiredConfigUri("end_session_endpoint");
|
|
||||||
|
|
||||||
if (mClientId == null) {
|
|
||||||
mRegistrationEndpointUri = getRequiredConfigWebUri("registration_endpoint_uri");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mDiscoveryUri = getRequiredConfigWebUri("discovery_uri");
|
|
||||||
}
|
|
||||||
|
|
||||||
mHttpsRequired = mConfigJson.optBoolean("https_required", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
String getConfigString(String propName) {
|
|
||||||
String value = mConfigJson.optString(propName);
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = value.trim();
|
|
||||||
if (TextUtils.isEmpty(value)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private String getRequiredConfigString(String propName)
|
|
||||||
throws InvalidConfigurationException {
|
|
||||||
String value = getConfigString(propName);
|
|
||||||
if (value == null) {
|
|
||||||
throw new InvalidConfigurationException(
|
|
||||||
propName + " is required but not specified in the configuration");
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
Uri getRequiredConfigUri(String propName)
|
|
||||||
throws InvalidConfigurationException {
|
|
||||||
String uriStr = getRequiredConfigString(propName);
|
|
||||||
Uri uri;
|
|
||||||
try {
|
|
||||||
uri = Uri.parse(uriStr);
|
|
||||||
} catch (Throwable ex) {
|
|
||||||
throw new InvalidConfigurationException(propName + " could not be parsed", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!uri.isHierarchical() || !uri.isAbsolute()) {
|
|
||||||
throw new InvalidConfigurationException(
|
|
||||||
propName + " must be hierarchical and absolute");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(uri.getEncodedUserInfo())) {
|
|
||||||
throw new InvalidConfigurationException(propName + " must not have user info");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(uri.getEncodedQuery())) {
|
|
||||||
throw new InvalidConfigurationException(propName + " must not have query parameters");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(uri.getEncodedFragment())) {
|
|
||||||
throw new InvalidConfigurationException(propName + " must not have a fragment");
|
|
||||||
}
|
|
||||||
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
Uri getRequiredConfigWebUri(String propName)
|
|
||||||
throws InvalidConfigurationException {
|
|
||||||
Uri uri = getRequiredConfigUri(propName);
|
|
||||||
String scheme = uri.getScheme();
|
|
||||||
if (TextUtils.isEmpty(scheme) || !("http".equals(scheme) || "https".equals(scheme))) {
|
|
||||||
throw new InvalidConfigurationException(
|
|
||||||
propName + " must have an http or https scheme");
|
|
||||||
}
|
|
||||||
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isRedirectUriRegistered() {
|
|
||||||
// ensure that the redirect URI declared in the configuration is handled by some activity
|
|
||||||
// in the app, by querying the package manager speculatively
|
|
||||||
Intent redirectIntent = new Intent();
|
|
||||||
redirectIntent.setPackage(mContext.getPackageName());
|
|
||||||
redirectIntent.setAction(Intent.ACTION_VIEW);
|
|
||||||
redirectIntent.addCategory(Intent.CATEGORY_BROWSABLE);
|
|
||||||
redirectIntent.setData(mRedirectUri);
|
|
||||||
|
|
||||||
return !mContext.getPackageManager().queryIntentActivities(redirectIntent, 0).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class InvalidConfigurationException extends Exception {
|
|
||||||
InvalidConfigurationException(String reason) {
|
|
||||||
super(reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
InvalidConfigurationException(String reason, Throwable cause) {
|
|
||||||
super(reason, cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
223
app/src/com/jc/jcfw/appauth/JConfiguration.kt
Normal file
223
app/src/com/jc/jcfw/appauth/JConfiguration.kt
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
package com.jc.jcfw.appauth
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.net.Uri
|
||||||
|
import android.text.TextUtils
|
||||||
|
import com.ctf.games.release.R
|
||||||
|
import net.openid.appauth.connectivity.ConnectionBuilder
|
||||||
|
import net.openid.appauth.connectivity.DefaultConnectionBuilder
|
||||||
|
import okio.Buffer
|
||||||
|
import okio.buffer
|
||||||
|
import okio.source
|
||||||
|
import org.json.JSONException
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.IOException
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
|
class JConfiguration(private val mContext: Context) {
|
||||||
|
private val mPrefs: SharedPreferences
|
||||||
|
private val mResources: Resources
|
||||||
|
private var mConfigJson: JSONObject? = null
|
||||||
|
private var mConfigHash: String? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a description of the configuration error, if the configuration is invalid.
|
||||||
|
*/
|
||||||
|
var configurationError: String? = null
|
||||||
|
var clientId: String? = null
|
||||||
|
private set
|
||||||
|
private var mScope: String? = null
|
||||||
|
private var mRedirectUri: Uri? = null
|
||||||
|
var endSessionRedirectUri: Uri? = null
|
||||||
|
private set
|
||||||
|
var discoveryUri: Uri? = null
|
||||||
|
private set
|
||||||
|
var authEndpointUri: Uri? = null
|
||||||
|
private set
|
||||||
|
var tokenEndpointUri: Uri? = null
|
||||||
|
private set
|
||||||
|
var endSessionEndpoint: Uri? = null
|
||||||
|
private set
|
||||||
|
var registrationEndpointUri: Uri? = null
|
||||||
|
private set
|
||||||
|
var userInfoEndpointUri: Uri? = null
|
||||||
|
private set
|
||||||
|
var isHttpsRequired = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
mPrefs = mContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
mResources = mContext.resources
|
||||||
|
try {
|
||||||
|
readConfiguration()
|
||||||
|
} catch (ex: InvalidConfigurationException) {
|
||||||
|
configurationError = ex.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the configuration has changed from the last known valid state.
|
||||||
|
*/
|
||||||
|
fun hasConfigurationChanged(): Boolean {
|
||||||
|
val lastHash = lastKnownConfigHash
|
||||||
|
return mConfigHash != lastHash
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the current configuration is valid.
|
||||||
|
*/
|
||||||
|
val isValid: Boolean
|
||||||
|
get() = configurationError == null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the current configuration should be accepted as the "last known valid"
|
||||||
|
* configuration.
|
||||||
|
*/
|
||||||
|
fun acceptConfiguration() {
|
||||||
|
mPrefs.edit().putString(KEY_LAST_HASH, mConfigHash).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
val scope: String
|
||||||
|
get() = mScope!!
|
||||||
|
val redirectUri: Uri
|
||||||
|
get() = mRedirectUri!!
|
||||||
|
val connectionBuilder: ConnectionBuilder
|
||||||
|
get() = DefaultConnectionBuilder.INSTANCE
|
||||||
|
private val lastKnownConfigHash: String?
|
||||||
|
private get() = mPrefs.getString(KEY_LAST_HASH, null)
|
||||||
|
|
||||||
|
@Throws(InvalidConfigurationException::class)
|
||||||
|
private fun readConfiguration() {
|
||||||
|
val configSource = mResources.openRawResource(R.raw.auth_config).source().buffer()
|
||||||
|
val configData = Buffer()
|
||||||
|
mConfigJson = try {
|
||||||
|
configSource.readAll(configData)
|
||||||
|
JSONObject(configData.readString(StandardCharsets.UTF_8))
|
||||||
|
} catch (ex: IOException) {
|
||||||
|
throw InvalidConfigurationException(
|
||||||
|
"Failed to read configuration: " + ex.message
|
||||||
|
)
|
||||||
|
} catch (ex: JSONException) {
|
||||||
|
throw InvalidConfigurationException(
|
||||||
|
"Unable to parse configuration: " + ex.message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
mConfigHash = configData.sha256().base64()
|
||||||
|
clientId = getConfigString("client_id")
|
||||||
|
mScope = getRequiredConfigString("authorization_scope")
|
||||||
|
mRedirectUri = getRequiredConfigUri("redirect_uri")
|
||||||
|
endSessionRedirectUri = getRequiredConfigUri("end_session_redirect_uri")
|
||||||
|
if (!isRedirectUriRegistered) {
|
||||||
|
throw InvalidConfigurationException(
|
||||||
|
"redirect_uri is not handled by any activity in this app! "
|
||||||
|
+ "Ensure that the appAuthRedirectScheme in your build.gradle file "
|
||||||
|
+ "is correctly configured, or that an appropriate intent filter "
|
||||||
|
+ "exists in your app manifest."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (getConfigString("discovery_uri") == null) {
|
||||||
|
authEndpointUri = getRequiredConfigWebUri("authorization_endpoint_uri")
|
||||||
|
tokenEndpointUri = getRequiredConfigWebUri("token_endpoint_uri")
|
||||||
|
userInfoEndpointUri = getRequiredConfigWebUri("user_info_endpoint_uri")
|
||||||
|
endSessionEndpoint = getRequiredConfigUri("end_session_endpoint")
|
||||||
|
if (clientId == null) {
|
||||||
|
registrationEndpointUri = getRequiredConfigWebUri("registration_endpoint_uri")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
discoveryUri = getRequiredConfigWebUri("discovery_uri")
|
||||||
|
}
|
||||||
|
isHttpsRequired = mConfigJson!!.optBoolean("https_required", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getConfigString(propName: String?): String? {
|
||||||
|
var value = mConfigJson!!.optString(propName) ?: return null
|
||||||
|
value = value.trim { it <= ' ' }
|
||||||
|
return if (TextUtils.isEmpty(value)) {
|
||||||
|
null
|
||||||
|
} else value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(InvalidConfigurationException::class)
|
||||||
|
private fun getRequiredConfigString(propName: String): String {
|
||||||
|
return getConfigString(propName)
|
||||||
|
?: throw InvalidConfigurationException(
|
||||||
|
"$propName is required but not specified in the configuration"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(InvalidConfigurationException::class)
|
||||||
|
fun getRequiredConfigUri(propName: String): Uri {
|
||||||
|
val uriStr = getRequiredConfigString(propName)
|
||||||
|
val uri: Uri
|
||||||
|
uri = try {
|
||||||
|
Uri.parse(uriStr)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
throw InvalidConfigurationException("$propName could not be parsed", ex)
|
||||||
|
}
|
||||||
|
if (!uri.isHierarchical || !uri.isAbsolute) {
|
||||||
|
throw InvalidConfigurationException(
|
||||||
|
"$propName must be hierarchical and absolute"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(uri.encodedUserInfo)) {
|
||||||
|
throw InvalidConfigurationException("$propName must not have user info")
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(uri.encodedQuery)) {
|
||||||
|
throw InvalidConfigurationException("$propName must not have query parameters")
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(uri.encodedFragment)) {
|
||||||
|
throw InvalidConfigurationException("$propName must not have a fragment")
|
||||||
|
}
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(InvalidConfigurationException::class)
|
||||||
|
fun getRequiredConfigWebUri(propName: String): Uri {
|
||||||
|
val uri = getRequiredConfigUri(propName)
|
||||||
|
val scheme = uri.scheme
|
||||||
|
if (TextUtils.isEmpty(scheme) || !("http" == scheme || "https" == scheme)) {
|
||||||
|
throw InvalidConfigurationException(
|
||||||
|
"$propName must have an http or https scheme"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return uri
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure that the redirect URI declared in the configuration is handled by some activity
|
||||||
|
// in the app, by querying the package manager speculatively
|
||||||
|
private val isRedirectUriRegistered: Boolean
|
||||||
|
private get() {
|
||||||
|
// ensure that the redirect URI declared in the configuration is handled by some activity
|
||||||
|
// in the app, by querying the package manager speculatively
|
||||||
|
val redirectIntent = Intent()
|
||||||
|
redirectIntent.setPackage(mContext.packageName)
|
||||||
|
redirectIntent.action = Intent.ACTION_VIEW
|
||||||
|
redirectIntent.addCategory(Intent.CATEGORY_BROWSABLE)
|
||||||
|
redirectIntent.data = mRedirectUri
|
||||||
|
return !mContext.packageManager.queryIntentActivities(redirectIntent, 0).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvalidConfigurationException : Exception {
|
||||||
|
internal constructor(reason: String?) : super(reason) {}
|
||||||
|
internal constructor(reason: String?, cause: Throwable?) : super(reason, cause) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "Configuration"
|
||||||
|
private const val PREFS_NAME = "config"
|
||||||
|
private const val KEY_LAST_HASH = "lastHash"
|
||||||
|
private var sInstance = WeakReference<JConfiguration?>(null)
|
||||||
|
fun getInstance(context: Context): JConfiguration {
|
||||||
|
var config = sInstance.get()
|
||||||
|
if (config == null) {
|
||||||
|
config = JConfiguration(context)
|
||||||
|
sInstance = WeakReference(config)
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,61 +0,0 @@
|
|||||||
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.ctf.games.release.R;
|
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class BiometricHelper {
|
|
||||||
|
|
||||||
public static final int ERROR_BIOMETRIC_FAIL = 100;
|
|
||||||
public static final int ERROR_BIOMETRIC_NO_CIPHER = 101;
|
|
||||||
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<BiometricResult> func) {
|
|
||||||
Executor executor = ContextCompat.getMainExecutor(activity.getApplicationContext());
|
|
||||||
return new BiometricPrompt(activity, executor, new BiometricPrompt.AuthenticationCallback() {
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationError(int errcode, @NonNull CharSequence errString) {
|
|
||||||
super.onAuthenticationError(errcode, errString);
|
|
||||||
Log.i(TAG, "Authentication error: " + errcode +" | "+ errString);
|
|
||||||
func.accept(new BiometricResult(errcode, (String) errString));
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationFailed() {
|
|
||||||
super.onAuthenticationFailed();
|
|
||||||
Log.i(TAG, "Authentication failed!");
|
|
||||||
func.accept(new BiometricResult(ERROR_BIOMETRIC_FAIL, "Authentication failed"));
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
|
||||||
super.onAuthenticationSucceeded(result);
|
|
||||||
Log.i(TAG, "Authentication succeeded!");
|
|
||||||
func.accept(new BiometricResult(result.getCryptoObject().getCipher()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
54
app/src/com/jc/jcfw/security/BiometricHelper.kt
Normal file
54
app/src/com/jc/jcfw/security/BiometricHelper.kt
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package com.jc.jcfw.security
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.biometric.BiometricPrompt.PromptInfo
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import com.ctf.games.release.R
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
object BiometricHelper {
|
||||||
|
const val ERROR_BIOMETRIC_FAIL = 100
|
||||||
|
const val ERROR_BIOMETRIC_NO_CIPHER = 101
|
||||||
|
private val TAG = BiometricHelper::class.java.simpleName
|
||||||
|
fun createPromptInfo(context: Context): PromptInfo {
|
||||||
|
return 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(BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.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()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createBiometricPrompt(
|
||||||
|
activity: FragmentActivity,
|
||||||
|
func: Consumer<BiometricResult>
|
||||||
|
): BiometricPrompt {
|
||||||
|
val executor = ContextCompat.getMainExecutor(activity.applicationContext)
|
||||||
|
return BiometricPrompt(activity, executor, object : BiometricPrompt.AuthenticationCallback() {
|
||||||
|
override fun onAuthenticationError(errcode: Int, errString: CharSequence) {
|
||||||
|
super.onAuthenticationError(errcode, errString)
|
||||||
|
Log.i(TAG, "Authentication error: $errcode | $errString")
|
||||||
|
func.accept(BiometricResult(errcode, errString as String))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationFailed() {
|
||||||
|
super.onAuthenticationFailed()
|
||||||
|
Log.i(TAG, "Authentication failed!")
|
||||||
|
func.accept(BiometricResult(ERROR_BIOMETRIC_FAIL, "Authentication failed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||||
|
super.onAuthenticationSucceeded(result)
|
||||||
|
Log.i(TAG, "Authentication succeeded!")
|
||||||
|
func.accept(BiometricResult(result.cryptoObject!!.cipher))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,61 +0,0 @@
|
|||||||
package com.jc.jcfw.security;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
|
|
||||||
public class BiometricResult {
|
|
||||||
private Cipher cipher;
|
|
||||||
private boolean error;
|
|
||||||
private int errcode;
|
|
||||||
private String errmsg;
|
|
||||||
|
|
||||||
public BiometricResult(Cipher cipher) {
|
|
||||||
if (null != cipher) {
|
|
||||||
this.cipher = cipher;
|
|
||||||
this.error = false;
|
|
||||||
} else {
|
|
||||||
this.error = true;
|
|
||||||
this.errcode = 101;
|
|
||||||
this.errmsg = "cipher is null";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BiometricResult(int errcode, String errmsg) {
|
|
||||||
this.error = true;
|
|
||||||
this.errcode = errcode;
|
|
||||||
this.errmsg = errmsg;
|
|
||||||
}
|
|
||||||
@Nullable
|
|
||||||
public Cipher getCipher() {
|
|
||||||
return cipher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCipher(Cipher cipher) {
|
|
||||||
this.cipher = cipher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isError() {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setError(boolean error) {
|
|
||||||
this.error = error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getErrcode() {
|
|
||||||
return errcode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setErrcode(int errcode) {
|
|
||||||
this.errcode = errcode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrmsg() {
|
|
||||||
return errmsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setErrmsg(String errmsg) {
|
|
||||||
this.errmsg = errmsg;
|
|
||||||
}
|
|
||||||
}
|
|
27
app/src/com/jc/jcfw/security/BiometricResult.kt
Normal file
27
app/src/com/jc/jcfw/security/BiometricResult.kt
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package com.jc.jcfw.security
|
||||||
|
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
|
||||||
|
class BiometricResult {
|
||||||
|
var cipher: Cipher? = null
|
||||||
|
var isError = false
|
||||||
|
var errcode = 0
|
||||||
|
var errmsg: String? = null
|
||||||
|
|
||||||
|
constructor(cipher: Cipher?) {
|
||||||
|
if (null != cipher) {
|
||||||
|
this.cipher = cipher
|
||||||
|
isError = false
|
||||||
|
} else {
|
||||||
|
isError = true
|
||||||
|
errcode = 101
|
||||||
|
errmsg = "cipher is null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(errcode: Int, errmsg: String?) {
|
||||||
|
isError = true
|
||||||
|
this.errcode = errcode
|
||||||
|
this.errmsg = errmsg
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +1,27 @@
|
|||||||
package com.jc.jcfw.security;
|
package com.jc.jcfw.security
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher
|
||||||
|
|
||||||
public interface CryptographyManager {
|
interface CryptographyManager {
|
||||||
/**
|
/**
|
||||||
* This method first gets or generates an instance of SecretKey and then initializes the Cipher
|
* 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.
|
* with the key. The secret key uses [ENCRYPT_MODE][Cipher.ENCRYPT_MODE] is used.
|
||||||
*/
|
*/
|
||||||
Cipher getInitializedCipherForEncryption(String keyName);
|
fun getInitializedCipherForEncryption(keyName: String?): Cipher?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method first gets or generates an instance of SecretKey and then initializes the Cipher
|
* 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.
|
* with the key. The secret key uses [DECRYPT_MODE][Cipher.DECRYPT_MODE] is used.
|
||||||
*/
|
*/
|
||||||
Cipher getInitializedCipherForDecryption(String keyName, byte[] initializationVector);
|
fun getInitializedCipherForDecryption(keyName: String?, initializationVector: ByteArray?): Cipher?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Cipher created with [getInitializedCipherForEncryption] is used here
|
* The Cipher created with [getInitializedCipherForEncryption] is used here
|
||||||
*/
|
*/
|
||||||
EncryptedData encryptData(String plaintext, Cipher cipher);
|
fun encryptData(plaintext: String?, cipher: Cipher?): EncryptedData?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Cipher created with [getInitializedCipherForDecryption] is used here
|
* The Cipher created with [getInitializedCipherForDecryption] is used here
|
||||||
*/
|
*/
|
||||||
String decryptData(byte[] ciphertext, Cipher cipher);
|
fun decryptData(ciphertext: ByteArray?, cipher: Cipher?): String?
|
||||||
}
|
}
|
@ -1,101 +0,0 @@
|
|||||||
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 android.util.Log;
|
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.KeyPairGenerator;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.KeyGenerator;
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
96
app/src/com/jc/jcfw/security/CryptographyManagerImpl.kt
Normal file
96
app/src/com/jc/jcfw/security/CryptographyManagerImpl.kt
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package com.jc.jcfw.security
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
class CryptographyManagerImpl : CryptographyManager {
|
||||||
|
override fun getInitializedCipherForEncryption(keyName: String?): Cipher? {
|
||||||
|
val cipher: Cipher
|
||||||
|
try {
|
||||||
|
cipher = this.cipher
|
||||||
|
val secretKey = getOrCreateSecretKey(keyName)
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException("Failed to initialize Cipher for encryption", e)
|
||||||
|
}
|
||||||
|
return cipher
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getInitializedCipherForDecryption(
|
||||||
|
keyName: String?,
|
||||||
|
initializationVector: ByteArray?
|
||||||
|
): Cipher? {
|
||||||
|
val cipher: Cipher
|
||||||
|
try {
|
||||||
|
cipher = this.cipher
|
||||||
|
val secretKey = getOrCreateSecretKey(keyName)
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, secretKey, IvParameterSpec(initializationVector))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException("Failed to initialize Cipher for decryption", e)
|
||||||
|
}
|
||||||
|
return cipher
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun encryptData(plaintext: String?, cipher: Cipher?): EncryptedData? {
|
||||||
|
return try {
|
||||||
|
val ciphertext = cipher!!.doFinal(plaintext!!.toByteArray(Charset.defaultCharset()))
|
||||||
|
EncryptedData(ciphertext, cipher.iv)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException("Failed to encrypt data", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun decryptData(ciphertext: ByteArray?, cipher: Cipher?): String? {
|
||||||
|
return try {
|
||||||
|
val plaintext = cipher!!.doFinal(ciphertext)
|
||||||
|
String(plaintext, StandardCharsets.UTF_8)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException("Failed to encrypt data", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:Throws(Exception::class)
|
||||||
|
private val cipher: Cipher
|
||||||
|
private get() = Cipher.getInstance(
|
||||||
|
KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||||
|
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
||||||
|
+ KeyProperties.ENCRYPTION_PADDING_PKCS7
|
||||||
|
)
|
||||||
|
|
||||||
|
@Throws(Exception::class)
|
||||||
|
private fun getOrCreateSecretKey(keyName: String?): SecretKey {
|
||||||
|
val ANDROID_KEYSTORE = "AndroidKeyStore"
|
||||||
|
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
|
||||||
|
keyStore.load(null) // Keystore must be loaded before it can be accessed
|
||||||
|
val existingKey = keyStore.getKey(keyName, null) as SecretKey
|
||||||
|
if (existingKey != null) {
|
||||||
|
return existingKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// if you reach here, then a new SecretKey must be generated for that keyName
|
||||||
|
val paramsBuilder = KeyGenParameterSpec.Builder(
|
||||||
|
keyName!!,
|
||||||
|
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
|
||||||
|
)
|
||||||
|
paramsBuilder.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
||||||
|
paramsBuilder.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
|
||||||
|
val KEY_SIZE = 256
|
||||||
|
paramsBuilder.setKeySize(KEY_SIZE)
|
||||||
|
paramsBuilder.setUserAuthenticationRequired(false)
|
||||||
|
paramsBuilder.setUserAuthenticationValidityDurationSeconds(30)
|
||||||
|
val keyGenParams = paramsBuilder.build()
|
||||||
|
val keyGenerator = KeyGenerator.getInstance(
|
||||||
|
KeyProperties.KEY_ALGORITHM_AES,
|
||||||
|
ANDROID_KEYSTORE
|
||||||
|
)
|
||||||
|
keyGenerator.init(keyGenParams)
|
||||||
|
return keyGenerator.generateKey()
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
3
app/src/com/jc/jcfw/security/EncryptedData.kt
Normal file
3
app/src/com/jc/jcfw/security/EncryptedData.kt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package com.jc.jcfw.security
|
||||||
|
|
||||||
|
class EncryptedData(val ciphertext: ByteArray, val initializationVector: ByteArray)
|
Loading…
x
Reference in New Issue
Block a user