移除java代码
This commit is contained in:
parent
ed0f5f42da
commit
17cfd36705
@ -219,16 +219,6 @@
|
||||
<category android:name="android.intent.category.default" />
|
||||
</intent-filter>
|
||||
</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>
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
|
@ -52,14 +52,14 @@ class BiometricActivity : AppCompatActivity() {
|
||||
}
|
||||
val encryptedData = cryptographyManager!!.encryptData(text, _result.cipher)
|
||||
val encryptedString =
|
||||
Base64.encodeToString(encryptedData.ciphertext, Base64.DEFAULT)
|
||||
Base64.encodeToString(encryptedData!!.ciphertext, Base64.DEFAULT)
|
||||
val ivString =
|
||||
Base64.encodeToString(encryptedData.initializationVector, Base64.DEFAULT)
|
||||
Log.i(TAG, "encrypted msg: $encryptedString")
|
||||
Log.i(TAG, "encrypted iv: $ivString")
|
||||
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")
|
||||
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
|
||||
* 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
|
||||
* 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
|
||||
*/
|
||||
EncryptedData encryptData(String plaintext, Cipher cipher);
|
||||
fun encryptData(plaintext: String?, cipher: Cipher?): EncryptedData?
|
||||
|
||||
/**
|
||||
* 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