Skip to content

Conversation

@p3dr0rv
Copy link
Contributor

@p3dr0rv p3dr0rv commented Jan 21, 2026

No description provided.

@p3dr0rv p3dr0rv requested a review from a team as a code owner January 21, 2026 01:43
Copilot AI review requested due to automatic review settings January 21, 2026 01:43
@github-actions
Copy link

❌ Work item link check failed. Description does not contain AB#{ID}.

Click here to Learn more.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This work-in-progress pull request adds OpenTelemetry instrumentation to secret key generation and retrieval operations in the Common library's cryptographic components. The PR introduces new telemetry attributes and adds span creation around key cryptographic operations to improve observability.

Changes:

  • Added 5 new telemetry attributes to track secret key serialization, key pair sources, device cryptographic capabilities, and failure history
  • Instrumented generateNewSecretKey() and readSecretKeyFromStorage() with dedicated spans and status tracking
  • Refactored wrapSecretKey() and unwrapSecretKey() to remove dedicated spans while retaining attribute reporting
  • Modified error history tracking from a list to a map structure to correlate failures with specific key generation specs

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 17 comments.

File Description
AttributeName.java Renamed key_pair_gen_encryptionPaddings to key_pair_gen_encryption_paddings for consistency; added 4 new attributes for tracking serializer IDs, key pair sources, and device capabilities
WrappedSecretKey.kt Added telemetry attribute setting in serialize/deserialize methods to track serializer version IDs
KeyStoreBackedSecretKeyProvider.kt Added span creation and lifecycle management for secret key generation/retrieval; refactored wrap/unwrap to remove dedicated spans; enhanced error tracking with spec-to-exception mapping
Comments suppressed due to low confidence (1)

common4j/src/main/com/microsoft/identity/common/java/opentelemetry/AttributeName.java:557

  • Each attribute added to AttributeName.java in the Common repo MUST also be defined in the AttributeName.java file in the broker repo (AzureAD/ad-accounts-for-android) for cross-repo consistency. The new attributes wrapped_secret_key_serializer_id, key_pair_from_keystore, key_pair_supported_paddings, and available_cipher_specs need to be added to the broker repo's AttributeName enum as well.
    /**
     * Indicates if the key pair was retrieved from keystore or generated anew.
     */
    key_pair_from_keystore,

    /**
     * Indicates the supported paddings for key pair in the device.
     */
    key_pair_supported_paddings,

    /**
     * Indicates the available cipher specs in the device.
     */
    available_cipher_specs,
}

Comment on lines 317 to 340
private fun unwrapSecretKey(
wrappedSecretKey: WrappedSecretKey,
keyPair: KeyPair
): SecretKey {
val methodTag = "$TAG:unwrapSecretKey"
val span = OTelUtility.createSpanFromParent(
SpanName.SecretKeyWrapping.name,
SpanExtension.current().spanContext
val cipherParamsSpec = getKeyPairCompatibleCipherSpecs(keyPair).firstOrNull { spec ->
spec.transformation.contains(wrappedSecretKey.cipherTransformation, ignoreCase = true)
} ?: throw ClientException(
ClientException.UNKNOWN_CRYPTO_ERROR,
"No compatible cipher specs found for key pair: $keyPair"
)
SpanExtension.current().setAttribute(
AttributeName.secret_key_wrapping_transformation.name,
cipherParamsSpec.transformation
)
Logger.info(methodTag, "Unwrapping secret key with cipher spec: $cipherParamsSpec")
return AndroidKeyStoreUtil.unwrap(
wrappedSecretKey.wrappedKeyData,
wrappedSecretKey.algorithm,
keyPair,
cipherParamsSpec.transformation,
cipherParamsSpec.algorithmParameterSpec
)

return try {
SpanExtension.makeCurrentSpan(span).use { _ ->
span.setAttribute(AttributeName.secret_key_wrapping_operation.name, "UNWRAP")
val cipherParamsSpec = getKeyPairCompatibleCipherSpecs(keyPair).firstOrNull { spec ->
spec.transformation.contains(wrappedSecretKey.cipherTransformation, ignoreCase = true)
} ?: throw ClientException(
ClientException.UNKNOWN_CRYPTO_ERROR,
"No compatible cipher specs found for key pair: $keyPair"
)

span.setAttribute(
AttributeName.secret_key_wrapping_transformation.name,
cipherParamsSpec.transformation
)
Logger.info(methodTag, "Unwrapping secret key with cipher spec: $cipherParamsSpec")
val key = AndroidKeyStoreUtil.unwrap(
wrappedSecretKey.wrappedKeyData,
wrappedSecretKey.algorithm,
keyPair,
cipherParamsSpec.transformation,
cipherParamsSpec.algorithmParameterSpec
)
span.setStatus(StatusCode.OK)
key
}
} catch (exception: Exception) {
Logger.error(methodTag, "Failed to wrap secret key", exception)
span.setStatus(StatusCode.ERROR)
span.recordException(exception)
throw exception
} finally {
span.end()
}
}
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Span removed but operation still needs to be tracked. The previous implementation created a dedicated span for the unwrap operation with proper lifecycle management (status codes, exception recording, span.end()). The refactored code removes the span entirely, leaving only an attribute set on the current span. This reduces observability for this cryptographic operation. Consider either: (1) restoring a dedicated span for unwrap operations to maintain detailed tracing, or (2) adding status code management and exception handling to properly track this operation within the parent span context.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +362 to +371
SpanExtension.current().apply {
setAttribute(
AttributeName.key_pair_supported_paddings.name,
supportedPaddings.toString()
)
setAttribute(
AttributeName.available_cipher_specs.name,
availableCipherSpecs.toString()
)
}
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High-cardinality sensitive data exposed in telemetry. The attributes key_pair_supported_paddings and available_cipher_specs are being set with full .toString() output of collections (lines 365, 369), which may contain unbounded, high-cardinality data that could overwhelm telemetry systems. Additionally, the full spec details could potentially expose device-specific cryptographic capabilities that should be privacy-classified. Consider: (1) limiting to count or a bounded summary (e.g., first 3 items), (2) hashing or bucketing the values, or (3) omitting these entirely and relying on lower-cardinality indicators like counts or boolean flags for specific capabilities.

Copilot generated this review using guidance from repository custom instructions.
errorMessageBuilder.append("${spec.print()}: ${exception.message};")
}
SpanExtension.current().setAttribute(
AttributeName.keypair_gen_exception.name,
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New telemetry attribute key_pair_gen_failure_history is never set. The attribute was added to the enum (line 363 in AttributeName.java) but the code uses keypair_gen_exception (line 525) instead. This appears to be a mismatch - either the new attribute name should be used here, or if keypair_gen_exception is the correct attribute, then key_pair_gen_failure_history is unused and should be removed from the enum to avoid confusion.

Suggested change
AttributeName.keypair_gen_exception.name,
AttributeName.key_pair_gen_failure_history.name,

Copilot uses AI. Check for mistakes.
Comment on lines +362 to +372
SpanExtension.current().apply {
setAttribute(
AttributeName.key_pair_supported_paddings.name,
supportedPaddings.toString()
)
setAttribute(
AttributeName.available_cipher_specs.name,
availableCipherSpecs.toString()
)
}

Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for new telemetry attribute setting in getKeyPairCompatibleCipherSpecs(). The method now sets attributes key_pair_supported_paddings and available_cipher_specs (lines 362-371). Tests should verify that these attributes are correctly set on the current span with the expected values from the supportedPaddings and availableCipherSpecs collections.

Copilot uses AI. Check for mistakes.
Comment on lines +426 to 428
logAndBuildErrorHistory(failures)
span.setStatus(StatusCode.OK)
return@use keyPair
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attribute set after successful key pair generation should occur before the return statement. The call to logAndBuildErrorHistory(failures) on line 426 happens after the span status is set to OK (line 427) but this is backwards from the recommended pattern. The error history should be logged before setting the success status. Additionally, if there were no failures (failures map is empty), this will set an empty or trivial string as the attribute value. Consider moving the call before line 427 and only calling it if failures is not empty.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +498 to 511
private fun handleAllFailures(failures: Map<IKeyGenSpec, Throwable>): Nothing {
val methodTag = "$TAG:handleAllFailures"
require(failures.isNotEmpty()) {
"No failures encountered, but no key pair generated. This should not happen."
}
val errorMessages = failures.joinToString(separator = "; ") { exception ->
"${exception.javaClass.simpleName}: ${exception.message ?: "Unknown error"}"
}

failures.forEach { exception ->
Logger.error(
methodTag,
"Key pair generation failed with: ${exception.message}",
exception
)
}
SpanExtension.current()
.setAttribute(AttributeName.keypair_gen_exception.name, errorMessages)

val finalError = failures.last()
val finalError = failures.values.last()
Logger.error(methodTag,
"All key generation attempts failed. Total failures: ${failures.size}",
finalError
)
SpanExtension.current().setStatus(StatusCode.ERROR)
SpanExtension.current().recordException(finalError)
throw ExceptionAdapter.clientExceptionFromException(finalError)
}
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refactored error handling loses important diagnostic information. The original code logged each individual failure with its full exception (lines 503-508 in the removed code), which was valuable for debugging. The new implementation only logs a single summary message with the count of failures and records only the last exception to the span (lines 504-509). This means intermediate failures and their stack traces are lost. Consider restoring the individual error logging within logAndBuildErrorHistory to preserve diagnostic detail while still building the consolidated history string for telemetry.

Copilot uses AI. Check for mistakes.
Comment on lines 519 to 528
private fun logAndBuildErrorHistory(failures: Map<IKeyGenSpec, Throwable>) {
val errorMessageBuilder = StringBuilder()
for( (spec, exception) in failures) {
errorMessageBuilder.append("${spec.print()}: ${exception.message};")
}
SpanExtension.current().setAttribute(
AttributeName.keypair_gen_exception.name,
errorMessageBuilder.toString()
)
}
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for new logAndBuildErrorHistory() method. This new method (lines 519-528) constructs an error history string from a map of failures and sets it as a telemetry attribute. Tests should verify: (1) correct formatting of the error history string with spec details and exception messages, (2) attribute is set on the current span, (3) behavior with empty map, single failure, and multiple failures.

Copilot uses AI. Check for mistakes.
Comment on lines 289 to 315
keyPair: KeyPair
): WrappedSecretKey {
val methodTag = "$TAG:wrapSecretKey"
val span = OTelUtility.createSpanFromParent(
SpanName.SecretKeyWrapping.name,
SpanExtension.current().spanContext
val cipherParamsSpec = getKeyPairCompatibleCipherSpecs(keyPair).firstOrNull()
?: throw ClientException(
ClientException.UNKNOWN_CRYPTO_ERROR,
"No compatible cipher specs found for key pair: $keyPair"
)
SpanExtension.current().setAttribute(
AttributeName.secret_key_wrapping_transformation.name, //elected
cipherParamsSpec.transformation
)
Logger.info(methodTag, "Wrapping secret key with cipher spec: $cipherParamsSpec")
val wrappedKey = AndroidKeyStoreUtil.wrap(
secretKey,
keyPair,
cipherParamsSpec.transformation,
cipherParamsSpec.algorithmParameterSpec
)
return WrappedSecretKey(
wrappedKeyData = wrappedKey,
algorithm = secretKey.algorithm,
cipherTransformation = cipherParamsSpec.transformation
)

return try {
SpanExtension.makeCurrentSpan(span).use { _ ->
span.setAttribute(AttributeName.secret_key_wrapping_operation.name, "WRAP")
val cipherParamsSpec = getKeyPairCompatibleCipherSpecs(keyPair).firstOrNull()
?: throw ClientException(
ClientException.UNKNOWN_CRYPTO_ERROR,
"No compatible cipher specs found for key pair: $keyPair"
)
span.setAttribute(
AttributeName.secret_key_wrapping_transformation.name,
cipherParamsSpec.transformation
)
Logger.info(methodTag, "Wrapping secret key with cipher spec: $cipherParamsSpec")
val wrappedKey = AndroidKeyStoreUtil.wrap(
secretKey,
keyPair,
cipherParamsSpec.transformation,
cipherParamsSpec.algorithmParameterSpec
)
span.setStatus(StatusCode.OK)
WrappedSecretKey(
wrappedKeyData = wrappedKey,
algorithm = secretKey.algorithm,
cipherTransformation = cipherParamsSpec.transformation
)
}
} catch (exception: Exception) {
Logger.error(methodTag, "Failed to wrap secret key", exception)
span.setStatus(StatusCode.ERROR)
span.recordException(exception)
throw exception
} finally {
span.end()
}

}
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Span removed but operation still needs to be tracked. The previous implementation created a dedicated span for the wrap operation with proper lifecycle management (status codes, exception recording, span.end()). The refactored code removes the span entirely, leaving only an attribute set on the current span. This reduces observability for this cryptographic operation. Consider either: (1) restoring a dedicated span for wrap operations to maintain detailed tracing, or (2) adding status code management and exception handling to properly track this operation within the parent span context.

Copilot generated this review using guidance from repository custom instructions.
p3dr0rv and others added 2 commits January 21, 2026 12:38
…izer_id for consistency in telemetry attributes
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants