When I enrol first and only fingerprint and generate KeyPair the PrivateKey gets invalidated when I use it for the second time. This happens only once. Am I the only one having this issue? Is there something wrong with my code?
I cannot use any other key as I'm using PrivateKey to sign data.
Steps:
Wipe all fingerprints
Enrol one fingerprint
Generate KeyPair and use FingerprintManager :: authenticate
During next use of FingerprintManager :: authenticate PrivateKey gets permanently invalidated. This happens only for the first time
Below the code where I generate the KeyPair
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keystore.load(null);
KeyPairGenerator generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
generator.initialize(new KeyGenParameterSpec.Builder("key_name", KeyProperties.PURPOSE_SIGN)
.setDigests(digest) // I have defined digest before
.setSignaturePaddings(paddings) // I have defined paddings before
.setUserAuthenticationRequired(true)
.build());
generator.generateKeyPair();
And here is the code where I invoke fingerprint authentication for data signing:
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
Signature signature = Signature.getInstance("signing_algorithm");
PrivateKey privateKey = (PrivateKey) keyStore.getKey("key_name", null);
signature.initSign(privateKey); // Here I get KeyPermanentlyInvalidatedException
CryptoObject crypto = new CryptoObject(signature);
FingerprintManager fingerprintManager = context.getSystemService(FingerprintManager.class);
CancellationSignal cancellationSignal = new CancellationSignal();
AuthenticationCallback authenticationCallback = new AuthenticationCallback() {
...
};
fingerprintManager.authenticate(crypto, cancelationSignal, 0, authenticationCallback, null);
i try this link and work perfectly .
First you need to set Minimum sdk look like the Picture
Second set Permission in Mainfest
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
Third
generateKey() function which generates an encryption key which is then stored securely on the device.
cipherInit() function that initializes the cipher that will be used to create the encrypted FingerprintManager.
CryptoObject instance and various other checks before initiating the authentication process which is implemented inside onCreate() method.
FingerPrintActivty.java
import android.Manifest;
import android.annotation.TargetApi;
import android.app.KeyguardManager;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
public class FingerprintActivity extends AppCompatActivity {
private KeyStore keyStore;
// Variable used for storing the key in the Android Keystore container
private static final String KEY_NAME = "androidHive";
private Cipher cipher;
private TextView textView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fingerprint);
// Initializing both Android Keyguard Manager and Fingerprint Manager
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
FingerprintManager fingerprintManager = (FingerprintManager) getSystemService(FINGERPRINT_SERVICE);
textView = (TextView) findViewById(R.id.errorText);
// Check whether the device has a Fingerprint sensor.
if(!fingerprintManager.isHardwareDetected()){
/**
* An error message will be displayed if the device does not contain the fingerprint hardware.
* However if you plan to implement a default authentication method,
* you can redirect the user to a default authentication activity from here.
* Example:
* Intent intent = new Intent(this, DefaultAuthenticationActivity.class);
* startActivity(intent);
*/
textView.setText("Your Device does not have a Fingerprint Sensor");
}else {
// Checks whether fingerprint permission is set on manifest
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
textView.setText("Fingerprint authentication permission not enabled");
}else{
// Check whether at least one fingerprint is registered
if (!fingerprintManager.hasEnrolledFingerprints()) {
textView.setText("Register at least one fingerprint in Settings");
}else{
// Checks whether lock screen security is enabled or not
if (!keyguardManager.isKeyguardSecure()) {
textView.setText("Lock screen security not enabled in Settings");
}else{
generateKey();
if (cipherInit()) {
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
FingerprintHandler helper = new FingerprintHandler(this);
helper.startAuth(fingerprintManager, cryptoObject);
}
}
}
}
}
}
#TargetApi(Build.VERSION_CODES.M)
protected void generateKey() {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
} catch (Exception e) {
e.printStackTrace();
}
KeyGenerator keyGenerator;
try {
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
throw new RuntimeException("Failed to get KeyGenerator instance", e);
}
try {
keyStore.load(null);
keyGenerator.init(new
KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(
KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
keyGenerator.generateKey();
} catch (NoSuchAlgorithmException |
InvalidAlgorithmParameterException
| CertificateException | IOException e) {
throw new RuntimeException(e);
}
}
#TargetApi(Build.VERSION_CODES.M)
public boolean cipherInit() {
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new RuntimeException("Failed to get Cipher", e);
}
try {
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME,
null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (KeyPermanentlyInvalidatedException e) {
return false;
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to init Cipher", e);
}
}
}
FingerprintAuthenticationHandler.Class
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.CancellationSignal;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.widget.TextView;
/**
* Created by whit3hawks on 11/16/16.
*/
public class FingerprintHandler extends FingerprintManager.AuthenticationCallback {
private Context context;
// Constructor
public FingerprintHandler(Context mContext) {
context = mContext;
}
public void startAuth(FingerprintManager manager, FingerprintManager.CryptoObject cryptoObject) {
CancellationSignal cancellationSignal = new CancellationSignal();
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
return;
}
manager.authenticate(cryptoObject, cancellationSignal, 0, this, null);
}
#Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
this.update("Fingerprint Authentication error\n" + errString, false);
}
#Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
this.update("Fingerprint Authentication help\n" + helpString, false);
}
#Override
public void onAuthenticationFailed() {
this.update("Fingerprint Authentication failed.", false);
}
#Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
this.update("Fingerprint Authentication succeeded.", true);
}
public void update(String e, Boolean success){
TextView textView = (TextView) ((Activity)context).findViewById(R.id.errorText);
textView.setText(e);
if(success){
textView.setTextColor(ContextCompat.getColor(context,R.color.colorPrimaryDark));
}
}
}
Hope it help.
you can see this one on github: hope it will help you : Confirm Credentials
Related
My requirement is to sign sha256 hash using keys generated in AndroidKeystore. but when I verify the signature validity it fails here are my observations using different scenarios.
we have tested this in the following scenarios using Google PIXEL 3A as strongbox enabled device
IsStrongBoxEnabled Algorithm Verification
TRUE NONEWithRSA FALSE
TRUE SHA256WithRSA TRUE
FALSE NONEWithRSA TRUE
FALSE SHA256WithRSA TRUE
TRUE NONEWithECDSA TRUE
TRUE SHA256WithECDSA TRUE
FALSE NONEWithECDSA TRUE
FALSE SHA256WithECDSA TRUE
Here is my sample test code
package com.company.samplekeystoreapp;
import android.os.Build;
import android.os.Bundle;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.StrongBoxUnavailableException;
import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import org.bouncycastle.util.encoders.Hex;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.Signature;
import java.security.spec.RSAKeyGenParameterSpec;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
private KeyPair keyPair;
private KeyStore keyStore;
private String aliasName;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
UUID uuid = UUID.randomUUID();
aliasName = "keyPrefix_" + uuid.toString();
keyPair = generateKeyPair(true);
KeyStore.Entry entry = keyStore.getEntry(aliasName, null);
if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
Log.w("SampleApp", "Not an instance of a PrivateKeyEntry");
}
Signature s = Signature.getInstance("NONEwithRSA");
if (entry instanceof KeyStore.PrivateKeyEntry) {
s.initSign(((KeyStore.PrivateKeyEntry) entry).getPrivateKey());
}
byte[] data = Hex.decode("d56ca469cd129c015f682724560baaf653914ef21fff1817512186d67b18c7f5");
s.update(data);
byte[] signature = s.sign();
if (entry instanceof KeyStore.PrivateKeyEntry) {
s.initVerify(((KeyStore.PrivateKeyEntry) entry).getCertificate());
}
s.update(data);
boolean valid = s.verify(signature);
Toast.makeText(MainActivity.this, "Status : " + valid, Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}
}
private KeyPair generateKeyPair(boolean isStrongBoxEnabled) throws Exception {
try{
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && isStrongBoxEnabled) {
keyPairGenerator.initialize(
new KeyGenParameterSpec.Builder(
aliasName, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512, KeyProperties.DIGEST_NONE, KeyProperties.DIGEST_SHA1)
.setIsStrongBoxBacked(true)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.build());
} else {
keyPairGenerator.initialize(
new KeyGenParameterSpec.Builder(
aliasName, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512, KeyProperties.DIGEST_NONE, KeyProperties.DIGEST_SHA1)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.build());
}
return keyPairGenerator.generateKeyPair();
}catch (Exception e) {
if (e instanceof StrongBoxUnavailableException) {
try {
return keyPair = generateKeyPair(false);
} catch (Exception ex) {
throw e;
}
}else {
throw e;
}
}
}
I am trying to learn how to implement safetynet with the reference of a fellow member in stackover. SafetyNet: package name always return null
The first section of the code is a complete code of SafetyNetVerifier
package com.example.stack;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Base64;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.safetynet.SafetyNet;
import com.google.android.gms.safetynet.SafetyNetApi;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Random;
public class SafetyNetVerifier implements GoogleApiClient.OnConnectionFailedListener {
private final Random mRandom = new SecureRandom();
private String mResult;
private GoogleApiClient mGoogleApiClient;
private FragmentActivity activity;
public SafetyNetVerifier(FragmentActivity activity) {
this.activity = activity;
buildGoogleApiClient();
sendSafetyNetRequest();
}
private byte[] getRequestNonce(String data) {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
byte[] bytes = new byte[24];
mRandom.nextBytes(bytes);
try {
byteStream.write(bytes);
byteStream.write(data.getBytes());
} catch (IOException e) {
return null;
}
return byteStream.toByteArray();
}
protected synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(activity)
.addApi(SafetyNet.API)
.enableAutoManage(activity, this)
.build();
}
private void sendSafetyNetRequest() {
Log.e("hqthao", "Sending SafetyNet API request.");
String nonceData = "Safety Net Sample: " + System.currentTimeMillis();
byte[] nonce = getRequestNonce(nonceData);
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
.setResultCallback(new ResultCallback<SafetyNetApi.AttestationResult>() {
#Override
public void onResult(SafetyNetApi.AttestationResult result) {
Status status = result.getStatus();
if (status.isSuccess()) {
mResult = result.getJwsResult();
Log.e("hqthao", "Success! SafetyNet result:\n" + mResult + "\n");
SafetyNetResponse response = parseJsonWebSignature(mResult);
Log.e("hqthao", response.toString());
}
}
});
}
#Nullable
private SafetyNetResponse parseJsonWebSignature(String jwsResult) {
if (jwsResult == null) {
return null;
}
//the JWT (JSON WEB TOKEN) is just a 3 base64 encoded parts concatenated by a . character
final String[] jwtParts = jwsResult.split("\\.");
if (jwtParts.length == 3) {
//we're only really interested in the body/payload
String decodedPayload = new String(Base64.decode(jwtParts[1], Base64.DEFAULT));
return SafetyNetResponse.parse(decodedPayload);
} else {
return null;
}
}
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
Log.e("hqthao", "Error connecting to Google Play Services." + connectionResult.getErrorMessage());
}
}
When i tried to debug, it will always stop at
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
May I know why is it so? I look at the Safetynet example provide by google and they will usually pair the API Key with the nonce. How can i change mGoogleApiClient to a API KEY?
private void sendSafetyNetRequest() {
Log.e("hqthao", "Sending SafetyNet API request.");
String nonceData = "Safety Net Sample: " + System.currentTimeMillis();
byte[] nonce = getRequestNonce(nonceData);
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
.setResultCallback(new ResultCallback<SafetyNetApi.AttestationResult>() {
#Override
public void onResult(SafetyNetApi.AttestationResult result) {
Status status = result.getStatus();
if (status.isSuccess()) {
mResult = result.getJwsResult();
Log.e("hqthao", "Success! SafetyNet result:\n" + mResult + "\n");
SafetyNetResponse response = parseJsonWebSignature(mResult);
Log.e("hqthao", response.toString());
}
}
});
}
You should use the SafetyNetClient interface to test. The sample code is as follows:
SafetyNetClient client = SafetyNet.getClient(getActivity());
Task<SafetyNetApi.AttestationResponse> task = client.attest(nonce, BuildConfig.API_KEY);
You can refer to the latest code on GitHub:
https://github.com/googlesamples/android-play-safetynet/blob/master/client/java/SafetyNetSample/Application/src/main/java/com/example/android/safetynetsample/SafetyNetSampleFragment.java
From my understanding, you will need an instance of SafetyNetClient to execute the attest method and not SafetyNetApi.attest, which is deprecated and now disabled
In order to get the Client, use:
SafetyNet.getClient(this).attest()
See here and the example here for more details
Im trying to implement fingerprint authentication inside app. while initializing KeyGenerator .it showing this error.
protected void generateKey() {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
} catch (Exception e) {
e.printStackTrace();
}
try {
keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore");
} catch (NoSuchAlgorithmException |
NoSuchProviderException e) {
throw new RuntimeException(
"Failed to get KeyGenerator instance", e);
}
try {
keyStore.load(null);
keyGenerator.init(new
KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(
KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
keyGenerator.generateKey();
} catch (NoSuchAlgorithmException |
InvalidAlgorithmParameterException
| CertificateException | IOException e) {
throw new RuntimeException(e);
}
Error:'KeyGenParameterSpec()' is not public in 'android.security.keystore.KeyGenParameterSpec'. Cannot be accessed from outside package
Below are two classes which you can use to authenticate with finger print. Their will be minor issues like permsission handling or any import. but this is working code of finger print authentication.
FingerPrintHandler class
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.fingerprint.FingerprintManager;
import android.Manifest;
import android.os.Build;
import android.os.CancellationSignal;
import android.support.v4.app.ActivityCompat;
import yasiriqbal.ethereum.R;
import yasiriqbal.ethereum.util.SessionManager;
#TargetApi(Build.VERSION_CODES.M)
public class FingerprintHandler extends FingerprintManager.AuthenticationCallback {
// You should use the CancellationSignal method whenever your app can no longer process user input, for example when your app goes
// into the background. If you don’t use this method, then other apps will be unable to access the touch sensor, including the lockscreen!//
private CancellationSignal cancellationSignal;
private Context context;
public FingerprintHandler(Context mContext) {
context = mContext;
}
//Implement the startAuth method, which is responsible for starting the fingerprint authentication process//
public void startAuth(FingerprintManager manager, FingerprintManager.CryptoObject cryptoObject) {
cancellationSignal = new CancellationSignal();
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
return;
}
manager.authenticate(cryptoObject, cancellationSignal, 0, this, null);
}
public void StopListener() {
try {
if (cancellationSignal != null)
cancellationSignal.cancel();
cancellationSignal = null;
}catch (Exception e)
{}
}
#Override
//onAuthenticationError is called when a fatal error has occurred. It provides the error code and error message as its parameters//
public void onAuthenticationError(int errMsgId, CharSequence errString) {
//I’m going to display the results of fingerprint authentication as a series of toasts.
//Here, I’m creating the message that’ll be displayed if an error occurs//
}
#Override
//onAuthenticationFailed is called when the fingerprint doesn’t match with any of the fingerprints registered on the device//
public void onAuthenticationFailed() {
}
#Override
//onAuthenticationHelp is called when a non-fatal error has occurred. This method provides additional information about the error,
//so to provide the user with as much feedback as possible I’m incorporating this information into my toast//
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
}
#Override
//onAuthenticationSucceeded is called when a fingerprint has been successfully matched to one of the fingerprints stored on the user’s device//
public void onAuthenticationSucceeded(
FingerprintManager.AuthenticationResult result) {
if (new SessionManager(context).getUsername() != null && !new SessionManager(context).getUsername().isEmpty()) {
String userName = new SessionManager(context).getUsername();
String pass = new SessionManager(context).getPass();
if (pass != null) {
((SigninActivity) context).requestLoginToServer(userName, pass);
((SigninActivity) context).isSignInButtonClicked = false;
}
}
//context.startActivity(new Intent(context, DashboardActivity.class));
}
}
Signin Class
import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.annotation.TargetApi;
import android.app.KeyguardManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Typeface;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.Handler;
import android.provider.ContactsContract;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TextInputEditText;
import android.support.design.widget.TextInputLayout;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v7.widget.AppCompatTextView;
import android.text.Editable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextWatcher;
import android.text.method.PasswordTransformationMethod;
import android.text.style.ForegroundColorSpan;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.gson.Gson;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import yasiriqbal.ethereum.EthereumApplication;
import yasiriqbal.ethereum.PermissionActivity;
import yasiriqbal.ethereum.R;
import yasiriqbal.ethereum.dashboad.DashboardActivity;
import yasiriqbal.ethereum.models.EthUserModel;
import yasiriqbal.ethereum.models.ServerMessage;
import yasiriqbal.ethereum.network.VolleyRequestHelper;
import yasiriqbal.ethereum.settings.ChangePasswordActivity;
import yasiriqbal.ethereum.signup.MobileCodeVerifyActivity;
import yasiriqbal.ethereum.signup.MobileNumberActivity;
import yasiriqbal.ethereum.signup.SignUpActivity;
import yasiriqbal.ethereum.util.Constants;
import yasiriqbal.ethereum.util.DialogErrorFragment;
import yasiriqbal.ethereum.util.DialogHelper;
import yasiriqbal.ethereum.util.Fonts;
import yasiriqbal.ethereum.util.MyUtils;
import yasiriqbal.ethereum.util.PermissionHandler;
import yasiriqbal.ethereum.util.SessionManager;
public class SigninActivity extends AccountAuthenticatorActivity implements View.OnClickListener, Response.Listener<JSONObject>, Response.ErrorListener {
public static final String PARAM_USER_PASS = "userpassword";
private static final int CODE_EMAIL_NOT_VERIFIED = 408;
private AppCompatTextView txt_loginscreen_not_register_member, txt_loginscreen_header,
txt_loginscreen_forgetpassword, txt_loginscreen_touchid;
private TextInputEditText edit_loginscreen_username, edit_loginscreen_password;
Button btn_loginscreen_signin;
PermissionHandler permissionHandler;
private TextInputLayout layout_loginscreen_password, layout_loginscreen_username;
private KeyStore keyStore;
private Cipher cipher;
private final int CODE_WRONG_PASSWORD = 401;
private final int CODE_INVALID_USER = 400;
private final int CODE_LOGIN_SUCCESS = 200;
public final int CODE_PROFILE_STATUS_COMPLETE = 2;
public final int CODE_MOBILE_NOT_VERIFY = 1;
private DialogHelper dialogHelper;
private final int ACCOUNT_PERMISSION_ID = 1234;
boolean isFromSetting = false;
private static final String KEY_NAME = "ethereum";
private Typeface qanelasRegularTypeFace;
private Typeface qanelasSemiBoldTypeFace;
private String previousUserName = "";
FingerprintHandler handler;
FingerprintManager mFingerprintManager;
public boolean isSignInButtonClicked = false;
FingerprintManager.CryptoObject cryptoObject;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sign_in);
if (permissionHandler.isMarshMallow()) {
authenticateWithFingerPrint();
if (permissionHandler.isPermissionAvailable(Manifest.permission.USE_FINGERPRINT)) {
setFingerprintConfig();
} else {
permissionHandler.requestPermission(Manifest.permission.USE_FINGERPRINT);
}
}//end of if is marshmallow
//dialogErrorFragment.show(getFragmentManager(),"dialog");
}//end of onCreate
#TargetApi(Build.VERSION_CODES.M)
private void authenticateWithFingerPrint() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
return;
}
mFingerprintManager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE);
if (mFingerprintManager.isHardwareDetected()) {
KeyguardManager mKeyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
if (mKeyguardManager.isKeyguardSecure()) {
generateKey();
if (initCipher()) {
cryptoObject = new FingerprintManager.CryptoObject(cipher);
handler = new FingerprintHandler(this);
handler.startAuth(mFingerprintManager, cryptoObject);
}
} else {
Toast.makeText(this, "Lock Screen Security not enabled in Settings.", Toast.LENGTH_LONG).show();
}
}
}
#TargetApi(Build.VERSION_CODES.M)
protected void generateKey() {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
} catch (Exception e) {
e.printStackTrace();
}
KeyGenerator keyGenerator;
try {
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
throw new RuntimeException("Failed to get KeyGenerator instance", e);
}
try {
keyStore.load(null);
keyGenerator.init(new
KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(
KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
keyGenerator.generateKey();
} catch (NoSuchAlgorithmException |
InvalidAlgorithmParameterException
| CertificateException | IOException e) {
throw new RuntimeException(e);
}
}
//Create a new method that we’ll use to initialize our cipher//
#TargetApi(Build.VERSION_CODES.M)
public boolean initCipher() {
try {
//Obtain a cipher instance and configure it with the properties required for fingerprint authentication//
cipher = Cipher.getInstance(
KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException |
NoSuchPaddingException e) {
throw new RuntimeException("Failed to get Cipher", e);
}
try {
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME,
null);
cipher.init(Cipher.ENCRYPT_MODE, key);
//Return true if the cipher has been initialized successfully//
return true;
} catch (KeyPermanentlyInvalidatedException e) {
//Return false if cipher initialization failed//
return false;
} catch (KeyStoreException | CertificateException
| UnrecoverableKeyException | IOException
| NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to init Cipher", e);
}
}
//xml initalization
private void initResources() {
//textViews initalize
}//end of initResources
#Override
protected void onResume() {
super.onResume();
}//end of onResume
//here to discover fingerprint hardware and set views
#TargetApi(Build.VERSION_CODES.M)
#SuppressWarnings("MissingPermission")
private void setFingerprintConfig() {
FingerprintManager mFingerprintManager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE);
if (mFingerprintManager.isHardwareDetected()) {
if (getPreviousUsername() != null && !getPreviousUsername().isEmpty() && mFingerprintManager.hasEnrolledFingerprints()) {
//TODO show finger print icon that this device support
} else {
//here means user is not enrolled for fingerpring
//TODO handle case when fingerprint not supported
}
}
}//end of setFingerprint
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (permissionHandler.PERMISSIONS_REQUEST == requestCode && grantResults.length > 0 && grantResults[0] > -1) {
setFingerprintConfig();
}
}
//onclick reciver for signin activity
#Override
public void onClick(View v) {
switch (v.getId()) {
default:
throw new IllegalArgumentException(v.getId() + " is not handle in onclick of signing activity");
}//end f switch
}//end of onClick
#Override
protected void onPause() {
super.onPause();
}
//here check if user profile complete then redirect to dashboard or then mobile verify
private void redirectUserAfterSuccessLogin(String data) throws JSONException {
if (!data.isEmpty()) {
Gson gsonForUser = new Gson();
new SessionManager(this).adToken(new JSONObject(data).getString("authenticationToken"));
EthUserModel ethUserModel = gsonForUser.fromJson(data, EthUserModel.class);
EthereumApplication.getInstance().setEthUserLoginObj(ethUserModel);
insertEthUserToContentProvider(ethUserModel);
if (!previousUserName.equals(ethUserModel.getEthUserName()) && !removeAccountFromAccountManager(ethUserModel, edit_loginscreen_password.getText().toString())) {
addAccountToAuthenticator(ethUserModel, edit_loginscreen_password.getText().toString());
startOtherActivity(ethUserModel);
//startSyncAdapter();
}//end of if for removeAccount return false
else {
startOtherActivity(ethUserModel);
//startSyncAdapter();
}//end of else
} else {
//here to show retry login
}
}//end of function
private void startOtherActivity(EthUserModel ethUserModel) {
if (isSignInButtonClicked)
new SessionManager(this).addData(edit_loginscreen_username.getText().toString(), edit_loginscreen_password.getText().toString());
if (txt_loginscreen_touchid.getVisibility() == View.VISIBLE)
try {
handler.StopListener();
} catch (Exception e) {
}
if (ethUserModel.getProfileStatus() == CODE_MOBILE_NOT_VERIFY) {
//redirect to mobile screen
startActivity(new Intent(this, MobileNumberActivity.class));
} else {
//redirect to dashboard if user not from setting
if (!getIntent().getBooleanExtra(Constants.ARG_IS_USER_FROM_SETTING, false)) {
if (EthereumApplication.getInstance().getEthUserLoginObj().getEthUserDoubleAuthenticationStatus() == Constants.ResultCode.CODE_DOUBLE_AUTHENTICATION_TRUE)
startActivity(new Intent(this, MobileCodeVerifyActivity.class)
.putExtra(Constants.MobileCodeVerifyActivity.IS_FROM_SIGNIN, true)
.putExtra(Constants.MobileNumberActivity.EXTRA_MOBILE_NUMBER, EthereumApplication.getInstance().getEthUserLoginObj().getMobileNum()));
else
startActivity(new Intent(this, DashboardActivity.class));
finish();
}
}//end of else
}//end of function
#Override
protected void onStop() {
super.onStop();
}
}//end of activity
I have some claims and I want to create JWT and sign it with a PrivateKey created in Fingerprint API.
This is the JWT claim -
Header:
{
"alg": "RS256”,
“kid”: “ABCDEDFkjsdfjaldfkjg”,
“auth_type” : “fingerprint” / "pin"
}
Payload:
{
“client_id”:”XXXXX-YYYYYY-ZZZZZZ”
}
Creating KeyPair for fingerprint -
import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.support.annotation.RequiresApi;
import android.util.Log;
import com.yourmobileid.mobileid.library.common.MIDCommons;
import org.jose4j.base64url.Base64;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.spec.RSAKeyGenParameterSpec;
#RequiresApi(api = Build.VERSION_CODES.M)
public class BiometricHelper {
public static final String KEY_NAME = "my_key";
static KeyPairGenerator mKeyPairGenerator;
private static String mKid;
private static KeyStore keyStore;
public static void init() {
try {
mKeyPairGenerator = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
throw new RuntimeException("Failed to get an instance of KeyPairGenerator", e);
}
mKid = MIDCommons.generateRandomString();
keyStore = null;
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
} catch (KeyStoreException e) {
throw new RuntimeException("Failed to get an instance of KeyStore", e);
}
createKeyPair();
}
/**
* Generates an asymmetric key pair in the Android Keystore. Every use of the private key must
* be authorized by the user authenticating with fingerprint. Public key use is unrestricted.
*/
public static void createKeyPair() {
try {
mKeyPairGenerator.initialize(
new KeyGenParameterSpec.Builder(
KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
.build());
mKeyPairGenerator.generateKeyPair();
} catch (InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
}
}
public static PrivateKey getPrivateKey() {
PrivateKey privateKey = null;
try {
keyStore.load(null);
privateKey = (PrivateKey) keyStore.getKey(KEY_NAME, null);
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | NoSuchAlgorithmException | IOException e) {
e.printStackTrace();
}
return privateKey;
}
public static PublicKey getPublicKey() {
PublicKey publicKey = null;
try {
keyStore.load(null);
publicKey = keyStore.getCertificate(KEY_NAME).getPublicKey();
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) {
e.printStackTrace();
}
return publicKey;
}
public static KeyStore getKeyStore(){
return keyStore;
}
public static String getPublicKeyStr() {
StringBuilder publicKey = new StringBuilder("-----BEGIN PUBLIC KEY-----\n");
publicKey.append(Base64.encode((getPublicKey().getEncoded())).replace("==",""));
publicKey.append("\n-----END PUBLIC KEY-----");
Log.d("Key==","\n"+publicKey);
return publicKey.toString();
}
public static String getKid() {
Log.d("mKid==","\n"+mKid);
return mKid;
}
}
And creating JWT this way -
#RequiresApi(api = Build.VERSION_CODES.M)
private String createJWT(){
JwtClaims claims = new JwtClaims();
claims.setClaim("client_id","”XXXXX-YYYYYY-ZZZZZZ”");
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toJson());
jws.setKey(BiometricHelper.getPrivateKey());
jws.setKeyIdHeaderValue(BiometricHelper.getKid());
jws.setHeader("auth_type","fingerprint");
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
String jwt = null;
try {
jwt = jws.getCompactSerialization();
} catch (JoseException e) {
e.printStackTrace();
}
System.out.println("JWT: " + jwt);
return jwt;
}
When i am doing this it am getting -
W/System.err: org.jose4j.lang.InvalidKeyException: The given key (algorithm=RSA) is not valid for SHA256withRSA
W/System.err: at org.jose4j.jws.BaseSignatureAlgorithm.initForSign(BaseSignatureAlgorithm.java:97)
W/System.err: at org.jose4j.jws.BaseSignatureAlgorithm.sign(BaseSignatureAlgorithm.java:68)
W/System.err: at org.jose4j.jws.JsonWebSignature.sign(JsonWebSignature.java:101)
I tried many other way for signing JWT with PrivateKey so far i did not find solution.
Any help is appreciated
You have created a key for encryption only, not for signing. Change
mKeyPairGenerator.initialize(
new KeyGenParameterSpec.Builder(
KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
.build());
With
mKeyPairGenerator.initialize(
new KeyGenParameterSpec.Builder(
KEY_NAME,
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setDigests(KeyProperties.DIGEST_SHA256)
.setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
.build());
Using gradle dependency
compile group: 'com.nimbusds', name: 'nimbus-jose-jwt', version: '4.41.1'
library I am able to fix the issue and sign JWT using AndroidKeyStoreRSAPrivateKey
Here RSASSASigner constructor which takes PrivateKey from Android KeyStore and this signer is used to sign JWSObject.
While looking for solution I did not find much information on this on Web so posting solution here for how to Sign JWT using PrivateKey from android Fingerprint API. Thanks pedrofb for you help :)
#RequiresApi(api = Build.VERSION_CODES.M)
private String createJWT(){
RSASSASigner signer = new RSASSASigner(BiometricHelper.getPrivateKey());
JSONObject message = new JSONObject();
message.put("client_id",mConfiguration.getClientID());
JWSObject jwsObject = new JWSObject(
new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(BiometricHelper.getKid())
.customParam("auth_type","touchid").build(),new Payload(message ));
try {
jwsObject.sign(signer);
} catch (JOSEException e) {
e.printStackTrace();
}
String jwt = jwsObject.serialize();
Log.d("JWT============","\n"+jwt);
return jwt;
}
While working on this thing i came across some bug reported in Nimbus-JOSE-JWT older version https://bitbucket.org/connect2id/nimbus-jose-jwt/issues/169/android-m-support
For anyone reading finding this question and the answers, it's worth mentioning that this key is NOT finger print protected - (setUserAuthenticationRequired(true) is not set on the key, and BiometricPrompt is not being used to approve the signing operation.
To do this correctly with jose4j you need to use it's jws.prepareSigningPrimitive() method - https://bitbucket.org/b_c/jose4j/issues/176/signing-not-possible-with-an has a discussion and a link to a full example.
Good day.
I need to save PKCS10 CSR in the external storage card.
However, the following code shows an error
java.io.FileNotFoundException: /storage/sdcard0pkcs10.req: open failed: EROFS (Read-only file system)
While AndoidManifest.xml correctly includes
I feel that the problem is directory path since it shows 0 rather than /
package exam.blowfishcipher;
import java.io.FileWriter;
import java.io.OutputStreamWriter;
import java.security.*;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import java.security.SecureRandom;
import javax.security.auth.x500.X500Principal;
import org.spongycastle.jce.PKCS10CertificationRequest;
import org.spongycastle.openssl.PEMWriter;
import android.os.Environment;
import android.util.*;
import android.widget.*;
public class PKCS10Generater
{
static {
Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider());
}
public static PKCS10CertificationRequest generateRequest(
KeyPair pair)
throws Exception
{
return new PKCS10CertificationRequest(
"SHA256withRSA",
new X500Principal("CN=Test CA Certificate"),
//new X500Principal("CN=end"),
pair.getPublic(),
null,
pair.getPrivate());
}
public static void pemEncodeToFile(String filename, Object obj, char[] password) throws Exception{
PEMWriter pw = new PEMWriter(new FileWriter(filename));
Log.e("Position", "PEMWriter");
if (password != null && password.length > 0) {
pw.writeObject(obj, "DESEDE", password, new SecureRandom());
} else {
pw.writeObject(obj);
}
pw.flush();
pw.close();
}
public static void reqGen() throws Exception
{
//create the keys
Log.e("Position", "reqGen");
KeyPair pair = Utils.generateRSAKeyPair();
//modified 20130203
PKCS10req pkcs10req = new PKCS10req();
PKCS10CertificationRequest request = pkcs10req.generateRequest(pair);
pemEncodeToFile(Environment.getExternalStorageDirectory()+"pkcs10.req", request, null);
Log.e("Position", "getExternalStorage");
PEMWriter pemWrt = new PEMWriter( new OutputStreamWriter(System.out));
pemWrt.writeObject(request);
pemWrt.close();
}
}
Just modify the line
pemEncodeToFile(Environment.getExternalStorageDirectory()+"pkcs10.req", request, null);
to
pemEncodeToFile(Environment.getExternalStorageDirectory()+"/pkcs10.req", request, null);