移除java代码

This commit is contained in:
CounterFire2023 2023-11-09 17:09:04 +08:00
parent ed0f5f42da
commit 17cfd36705
18 changed files with 544 additions and 906 deletions

View File

@ -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"

View File

@ -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!!))
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View 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
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View 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
}
}
}

View File

@ -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()));
}
});
}
}

View 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))
}
})
}
}

View File

@ -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;
}
}

View 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
}
}

View File

@ -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?
}

View File

@ -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();
}
}

View 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()
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,3 @@
package com.jc.jcfw.security
class EncryptedData(val ciphertext: ByteArray, val initializationVector: ByteArray)