@@ -483,6 +483,68 @@ public static synchronized SecretKey unwrap(final byte[] wrappedKeyBlob,
483483 throw clientException ;
484484 }
485485
486+ /**
487+ * Populate attributes from an InvalidKeyException, attempting to extract details from a nested
488+ * KeyStoreException if available (API Level 33+).
489+ */
490+ private static AttributesBuilder createAttributesBuilderFromInvalidKeyException (final InvalidKeyException exception ) {
491+ String ksMessage ;
492+ final String errorType ;
493+ final String ksNumericErrorCode ;
494+
495+ // Check API Level before attempting to extract KeyStoreException details
496+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
497+ final android .security .KeyStoreException keyStoreException = findKeyStoreException (exception );
498+ if (keyStoreException != null ) {
499+ ksMessage = keyStoreException .getMessage ();
500+ if (ksMessage == null ) {
501+ ksMessage = "Keystore exception found, no error message" ;
502+ }
503+ errorType = "KeyStoreException" ;
504+ ksNumericErrorCode = String .valueOf (keyStoreException .getNumericErrorCode ());
505+ } else {
506+ ksMessage = "No keystore exception found" ;
507+ errorType = "InvalidKeyException" ;
508+ ksNumericErrorCode = "" ;
509+ }
510+ } else {
511+ ksMessage = "API Level below 33, keystore exception not available" ;
512+ errorType = "InvalidKeyException" ;
513+ ksNumericErrorCode = "" ;
514+ }
515+
516+ return Attributes .builder ()
517+ .put (AttributeName .error_type .name (), errorType )
518+ .put (AttributeName .keystore_exception_stack_trace .name (), ThrowableUtil .getStackTraceAsString (exception ))
519+ .put (AttributeName .keystore_exception_message .name (), ksMessage )
520+ .put (AttributeName .keystore_numeric_error_code .name (), ksNumericErrorCode );
521+ }
522+
523+ /**
524+ * Searches the causal chain of the given throwable for an instance of
525+ * {@link android.security.KeyStoreException}.
526+ *
527+ * @param throwable The throwable to search.
528+ * @return The found KeyStoreException, or null if none was found or the API level is below 33.
529+ */
530+ private static @ Nullable android .security .KeyStoreException findKeyStoreException (@ NonNull Throwable throwable ) {
531+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
532+ // Check up to a max depth to avoid infinite loops in case of circular references
533+ int count = 0 ;
534+ while (throwable != null && count < KEYSTORE_EXCEPTION_CAUSE_CHAIN_MAX_DEPTH ) {
535+ if (throwable instanceof android .security .KeyStoreException ) {
536+ return (android .security .KeyStoreException ) throwable ;
537+ }
538+ throwable = throwable .getCause ();
539+ count ++;
540+ }
541+
542+ return null ;
543+ } else {
544+ return null ;
545+ }
546+ }
547+
486548 /**
487549 * Returns encryption paddings supported by a KeyStore key pair.
488550 * <p>
@@ -498,19 +560,15 @@ public static synchronized List<String> getKeyPairEncryptionPaddings(@NonNull fi
498560 try {
499561 final PrivateKey privateKey = keyPair .getPrivate ();
500562 final KeyFactory keyFactory = KeyFactory .getInstance (privateKey .getAlgorithm (), ANDROID_KEY_STORE_TYPE );
501- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
502- final KeyInfo keyInfo = keyFactory .getKeySpec (privateKey , KeyInfo .class );
503- final List <String > encryptionPaddings = new ArrayList <>();
504- // keyInfo.getEncryptionPaddings() returns a list of encryption paddings supported by the key.
505- // We remove the "Padding" suffix from each padding name to match the expected format.
506- for (final String padding : keyInfo .getEncryptionPaddings ()) {
507- encryptionPaddings .add (padding .replace ("Padding" , "" ));
508- }
509- Logger .info (methodTag , "Supported encryption paddings: " + encryptionPaddings );
510- return encryptionPaddings ;
511- } else {
512- Logger .warn (methodTag , "KeyInfo not available on API < 23" );
563+ final KeyInfo keyInfo = keyFactory .getKeySpec (privateKey , KeyInfo .class );
564+ final List <String > encryptionPaddings = new ArrayList <>();
565+ // keyInfo.getEncryptionPaddings() returns a list of encryption paddings supported by the key.
566+ // We remove the "Padding" suffix from each padding name to match the expected format.
567+ for (final String padding : keyInfo .getEncryptionPaddings ()) {
568+ encryptionPaddings .add (padding .replace ("Padding" , "" ));
513569 }
570+ Logger .info (methodTag , "Supported encryption paddings: " + encryptionPaddings );
571+ return encryptionPaddings ;
514572 } catch (final Exception e ) {
515573 Logger .warn (methodTag , "Failed to retrieve key padding information" + ": " + e .getMessage ());
516574 }
0 commit comments