5555
5656import edu .umd .cs .findbugs .annotations .Nullable ;
5757import io .opentelemetry .api .common .Attributes ;
58+ import io .opentelemetry .api .common .AttributesBuilder ;
5859import io .opentelemetry .api .metrics .LongCounter ;
5960import lombok .NonNull ;
6061
@@ -84,6 +85,11 @@ public class AndroidKeyStoreUtil {
8485 */
8586 private static final String ANDROID_KEY_STORE_TYPE = "AndroidKeyStore" ;
8687
88+ /**
89+ * Max length of stack trace to search for a keystore exception
90+ */
91+ private static int KEYSTORE_EXCEPTION_CAUSE_CHAIN_MAX_DEPTH = 20 ;
92+
8793 private AndroidKeyStoreUtil () {
8894 }
8995
@@ -447,12 +453,11 @@ public static synchronized SecretKey unwrap(@NonNull final byte[] wrappedKeyBlob
447453 exception
448454 );
449455 if (exception instanceof InvalidKeyException ) {
450- final Attributes attributes = Attributes . builder ( )
456+ final Attributes attributes = createAttributesBuilderFromInvalidKeyException (( InvalidKeyException ) exception )
451457 .put (AttributeName .keystore_operation .name (), "unwrap" )
452458 .put (AttributeName .error_code .name (), errCode )
453- .put (AttributeName .error_type .name (), clientException .getClass ().getSimpleName ())
454- .put (AttributeName .keystore_exception_stack_trace .name (), ThrowableUtil .getStackTraceAsString (clientException ))
455459 .build ();
460+
456461 sFailedAndroidKeyStoreUnwrapOperationCount .add (1 , attributes );
457462 }
458463 Logger .error (
@@ -464,4 +469,65 @@ public static synchronized SecretKey unwrap(@NonNull final byte[] wrappedKeyBlob
464469 throw clientException ;
465470 }
466471
472+ /**
473+ * Populate attributes from an InvalidKeyException, attempting to extract details from a nested
474+ * KeyStoreException if available (API Level 33+).
475+ */
476+ private static AttributesBuilder createAttributesBuilderFromInvalidKeyException (final InvalidKeyException exception ) {
477+ String ksMessage ;
478+ final String errorType ;
479+ final String ksNumericErrorCode ;
480+
481+ // Check API Level before attempting to extract KeyStoreException details
482+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
483+ final android .security .KeyStoreException keyStoreException = findKeyStoreException (exception );
484+ if (keyStoreException != null ) {
485+ ksMessage = keyStoreException .getMessage ();
486+ if (ksMessage == null ) {
487+ ksMessage = "Keystore exception found, no error message" ;
488+ }
489+ errorType = "KeyStoreException" ;
490+ ksNumericErrorCode = String .valueOf (keyStoreException .getNumericErrorCode ());
491+ } else {
492+ ksMessage = "No keystore exception found" ;
493+ errorType = "InvalidKeyException" ;
494+ ksNumericErrorCode = "" ;
495+ }
496+ } else {
497+ ksMessage = "API Level below 33, keystore exception not available" ;
498+ errorType = "InvalidKeyException" ;
499+ ksNumericErrorCode = "" ;
500+ }
501+
502+ return Attributes .builder ()
503+ .put (AttributeName .error_type .name (), errorType )
504+ .put (AttributeName .keystore_exception_stack_trace .name (), ThrowableUtil .getStackTraceAsString (exception ))
505+ .put (AttributeName .keystore_exception_message .name (), ksMessage )
506+ .put (AttributeName .keystore_numeric_error_code .name (), ksNumericErrorCode );
507+ }
508+
509+ /**
510+ * Searches the causal chain of the given throwable for an instance of
511+ * {@link android.security.KeyStoreException}.
512+ *
513+ * @param throwable The throwable to search.
514+ * @return The found KeyStoreException, or null if none was found or the API level is below 33.
515+ */
516+ private static @ Nullable android .security .KeyStoreException findKeyStoreException (@ NonNull Throwable throwable ) {
517+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
518+ // Check up to a max depth to avoid infinite loops in case of circular references
519+ int count = 0 ;
520+ while (throwable != null && count < KEYSTORE_EXCEPTION_CAUSE_CHAIN_MAX_DEPTH ) {
521+ if (throwable instanceof android .security .KeyStoreException ) {
522+ return (android .security .KeyStoreException ) throwable ;
523+ }
524+ throwable = throwable .getCause ();
525+ count ++;
526+ }
527+
528+ return null ;
529+ } else {
530+ return null ;
531+ }
532+ }
467533}
0 commit comments