Skip to content

Add KeyMetadata data class for symmetric key rotation, Fixes AB#3546146#3023

Merged
fadidurah merged 4 commits intofadi/secret-key-rotationfrom
copilot/add-keymetadata-data-class
Mar 19, 2026
Merged

Add KeyMetadata data class for symmetric key rotation, Fixes AB#3546146#3023
fadidurah merged 4 commits intofadi/secret-key-rotationfrom
copilot/add-keymetadata-data-class

Conversation

Copy link
Contributor

Copilot AI commented Mar 17, 2026

Foundational immutable data class for tracking symmetric encryption key versions, enabling SDL-compliant key rotation in the Android Broker. Each key instance captures its version ID, creation timestamp, algorithm, key size, and deprecation status (decrypt-only flag).

Changes

common4j/.../crypto/KeyMetadata.java

  • Immutable final class using Lombok @Getter and @Builder annotations to eliminate boilerplate
  • Fields: versionId, createdAtMillis, algorithm (default AES/CBC/PKCS5Padding), keySize (default 256), deprecated
  • Fail-fast validation in constructor: blank versionId throws IllegalStateException; non-positive keySize throws IllegalArgumentException
  • toJson() / fromJson(String) via org.json for persistence by the KeyVersionRegistry
  • fromJson() uses opt* APIs for optional fields (algorithm, keySize, deprecated) so deserialization is backward-compatible with older persisted JSON
KeyMetadata key = KeyMetadata.builder()
        .versionId("K001")
        .createdAtMillis(System.currentTimeMillis())
        .deprecated(false)
        .build();

String json = key.toJson();
KeyMetadata restored = KeyMetadata.fromJson(json);

common4j/.../crypto/KeyMetadataTest.java

Unit tests covering builder defaults, field validation, JSON round-trip, deprecated flag semantics, backward-compatible deserialization of JSON missing optional fields, and malformed JSON error handling.

Original prompt

[Symmetric Key Rotation] Add KeyMetadata data class

Fixes AB#3546146

Objective

Add a KeyMetadata data class to common4j that stores version information for symmetric encryption keys. This is the foundational data structure for the symmetric key rotation feature.

Context

The Android Broker needs to support multiple symmetric key versions to enable SDL-compliant key rotation. Each key needs metadata including version ID, creation timestamp, algorithm, and deprecation status. This data class will be used by the KeyVersionRegistry (in common) and the encryption managers (in broker).

Technical Requirements

  • Create new file common4j/src/main/com/microsoft/identity/common/java/crypto/KeyMetadata.java
  • Implement as an immutable data class with the following fields:
    • versionId (String): Key identifier like K001, K002
    • createdAtMillis (long): Unix timestamp of key creation
    • algorithm (String): Encryption algorithm, default AES/CBC/PKCS5Padding
    • keySize (int): Key size in bits, default 256
    • isDeprecated (boolean): If true, key can only decrypt (not encrypt)
  • Add JSON serialization/deserialization methods (toJson(), fromJson(String)) using org.json
  • Follow existing patterns in common4j for data classes
  • Add appropriate JavaDoc comments
  • Add copyright header to the new file

Scope

In scope: KeyMetadata data class with all fields, JSON serialization/deserialization, Builder pattern for construction.
Out of scope: Persistence logic (handled by KeyVersionRegistry), Key generation logic (handled elsewhere).

Files to Create

  • common4j/src/main/com/microsoft/identity/common/java/crypto/KeyMetadata.java

Acceptance Criteria

  • KeyMetadata class exists with all required fields
  • toJson() produces valid JSON representation
  • fromJson(String) reconstructs the object correctly
  • Class is immutable (final fields, no setters)
  • JavaDoc comments document each field
  • Compile check passes: ./gradlew :common4j:compileJava

Follow .github/copilot-instructions.md strictly.


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

@github-actions
Copy link

✅ Work item link check complete. Description contains link AB#3546146 to an Azure Boards work item.

@github-actions github-actions bot changed the title [WIP] [AB#3546146] Add KeyMetadata data class [WIP] [AB#3546146] Add KeyMetadata data class, Fixes AB#3546146 Mar 17, 2026
Co-authored-by: fadidurah <88730756+fadidurah@users.noreply.github.com>
Copilot AI changed the title [WIP] [AB#3546146] Add KeyMetadata data class, Fixes AB#3546146 Add KeyMetadata data class for symmetric key rotation Mar 17, 2026
Copilot AI requested a review from fadidurah March 17, 2026 23:44
@github-actions github-actions bot changed the title Add KeyMetadata data class for symmetric key rotation Add KeyMetadata data class for symmetric key rotation, Fixes AB#3546146 Mar 17, 2026
@fadidurah fadidurah marked this pull request as ready for review March 18, 2026 23:18
@fadidurah fadidurah requested a review from a team as a code owner March 18, 2026 23:18
Copilot AI review requested due to automatic review settings March 18, 2026 23:18
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

Adds a foundational immutable metadata model in common4j to represent symmetric key versions for upcoming key-rotation work (intended to be consumed by a KeyVersionRegistry and broker encryption managers).

Changes:

  • Introduces KeyMetadata (immutable, builder-based) with JSON serialization/deserialization for persistence.
  • Adds unit tests validating builder behavior and JSON round-tripping.

Reviewed changes

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

File Description
common4j/src/main/com/microsoft/identity/common/java/crypto/KeyMetadata.java New immutable key-version metadata type with a Builder and org.json (de)serialization helpers.
common4j/src/test/com/microsoft/identity/common/java/crypto/KeyMetadataTest.java Unit tests for builder defaults/validation and JSON round-trip behavior.

@fadidurah fadidurah changed the base branch from dev to fadi/secret-key-rotation March 18, 2026 23:55
@fadidurah
Copy link
Contributor

@copilot we can make this class much more concise using Lombok's Getter and Builder annotations

Co-authored-by: fadidurah <88730756+fadidurah@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 19, 2026

@copilot we can make this class much more concise using Lombok's Getter and Builder annotations

Done in 7e519e3. The class now uses @Getter + @Builder + @Builder.Default, removing ~120 lines of boilerplate (all manual getters and the entire inner Builder class). Validation is kept in a private constructor that Lombok's generated build() calls. I also took the opportunity to fix fromJson() to use opt* APIs for optional fields and improve the JSON assertion test to parse the JSON object and check each field directly.

Copilot AI requested a review from fadidurah March 19, 2026 00:07
@fadidurah fadidurah added the No-Changelog This Pull-Request has no associated changelog entry. label Mar 19, 2026
@fadidurah fadidurah merged commit 8712d41 into fadi/secret-key-rotation Mar 19, 2026
24 of 26 checks passed
fadidurah added a commit that referenced this pull request Mar 20, 2026
…nFlight entry, Fixes AB#3546155 (#3033)

Adds comprehensive JVM-based unit tests for `KeyVersionRegistry`
(introduced in #3031) using Robolectric, covering the full symmetric key
lifecycle. Also removes the mistakenly added
`SYMMETRIC_KEY_RECOMMENDED_AGE_DAYS` flight from `CommonFlight` (it
belongs solely in `BrokerFlight`, where `RotationOrchestrator` reads
it).

## Test coverage

- **`generateNewKey`** — metadata fields (`versionId` K001/K002
sequencing, `algorithm`, `keySize`, `isDeprecated=false`), wrapped key
file creation, no auto-promotion to active
- **`getActiveKey` / `setActiveKey`** — null on empty registry,
promotion, throws on unknown version
- **`getKeyByVersion`** — hit and miss cases
- **`deprecateKey` / `getDeprecatedKeys`** — marks deprecated, does not
affect sibling keys, idempotent, throws on unknown version, filtered
list
- **`loadSecretKey`** — returns AES `SecretKey`, deterministic re-load
(same encoded bytes), throws on unknown version
- **`pruneExpiredKeys`** — age-based pruning, active key is never
pruned, key file deleted on prune, boundary condition (1 s under
threshold kept), expired non-deprecated keys are pruned
- **Multi-key lifecycle** — generate → deprecate → generate new → assert
both accessible with correct active/deprecated state

## Setup

- `@RunWith(RobolectricTestRunner::class)` + `@Config(sdk =
[Build.VERSION_CODES.P])`, consistent with existing module tests
- `overrideKeyCreationTimestamp()` helper manipulates SharedPreferences
directly to simulate aged keys without real-time delays
- `cleanUp()` runs before/after each test: clears SharedPreferences,
removes the AndroidKeyStore wrapping key pair, deletes wrapped key files
K001–K010
- `AndroidKeyStoreUtil` static methods are stubbed via `mockkStatic` to
avoid Robolectric `KeyStoreException` when generating RSA wrapping key
pairs

## CommonFlight cleanup

Removed `SYMMETRIC_KEY_RECOMMENDED_AGE_DAYS` from `CommonFlight`. This
entry was added by mistake in PR #3034 — it is only consumed by
`RotationOrchestrator` in broker (via `BrokerFlight`) and has no callers
in the common library. `SYMMETRIC_KEY_MAX_AGE_DAYS` remains as the sole
symmetric-key flight in `CommonFlight`, used by
`KeyVersionRegistry.pruneExpiredKeys()`.

<!-- START COPILOT CODING AGENT SUFFIX -->



<!-- START COPILOT ORIGINAL PROMPT -->



<details>

<summary>Original prompt</summary>

> ## [Symmetric Key Rotation][Common] Add KeyVersionRegistry unit tests
> 
> Fixes
[AB#3546155](https://identitydivision.visualstudio.com/fac9d424-53d2-45c0-91b5-ef6ba7a6bf26/_workitems/edit/3546155)
> 
> ### Objective
> 
> Write comprehensive unit tests for KeyVersionRegistry (merged in PR
#3031 on the fadi/secret-key-rotation branch). Cover key generation,
deprecation, metadata persistence, pruning, and edge cases.
> 
> ### Context
> 
> KeyVersionRegistry was implemented in PR #3031 and merged into the
fadi/secret-key-rotation branch of
AzureAD/microsoft-authentication-library-common-for-android. It manages
versioned encryption keys with metadata stored in SharedPreferences.
Tests should validate the registry's core operations.
> 
> ### Technical Requirements
> 
> 1. Create test file:
common/common/src/test/java/com/microsoft/identity/common/crypto/KeyVersionRegistryTest.kt
> 2. Use JUnit 4/5 with Robolectric (existing pattern in common module
tests)
> 3. Test cases to implement:
> - generateNewKey creates key with isDeprecated=false and current
timestamp
> - generateNewKey assigns incrementing version IDs (K001, K002, etc.)
> - getActiveKey returns the most recently generated non-deprecated key
>    - getKeyByVersion returns correct metadata for known version
>    - getKeyByVersion returns null for unknown version
>    - deprecateKey marks key as deprecated (isDeprecated=true)
>    - deprecateKey does not affect other keys
>    - loadSecretKey returns AES SecretKey for valid version
>    - loadSecretKey throws for unknown version
> - pruneExpiredKeys removes deprecated keys older than retention period
> - pruneExpiredKeys does NOT remove non-deprecated (active) keys
regardless of age
> - pruneExpiredKeys does NOT remove deprecated keys within retention
period
> - Multiple key lifecycle: generate -> deprecate old -> generate new ->
verify both accessible
>    - getDeprecatedKeys returns only deprecated keys
> 4. Use @RunWith(RobolectricTestRunner.class) pattern
> 5. Use ApplicationProvider.getApplicationContext() for context
> 
> ### Reference Files
> 
> - KeyVersionRegistry on fadi/secret-key-rotation branch (PR #3031)
> - KeyMetadata data class (PR #3023)
> - Existing test patterns:
common/common/src/test/java/com/microsoft/identity/common/internal/cache/SharedPreferencesFileManagerTest.java
> - Design doc test cases: design-docs/[Android] Symmetric Key
Rotation/symmetric-key-rotation.md (Testing Strategy section)
> 
> ### Acceptance Criteria
> 
> - Test file created at specified path
> - All test cases pass: ./gradlew :common:testDebugUnitTest --tests
*.KeyVersionRegistryTest
> - Tests cover happy path and edge cases
> - Uses Robolectric for Android context
> - Copyright header included
> - KDoc comments on test class
> 
> Follow .github/copilot-instructions.md strictly.
> 


</details>



<!-- START COPILOT CODING AGENT TIPS -->
---

💬 Send tasks to Copilot coding agent from
[Slack](https://gh.io/cca-slack-docs) and
[Teams](https://gh.io/cca-teams-docs) to turn conversations into code.
Copilot posts an update in your thread when it's finished.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: fadidurah <88730756+fadidurah@users.noreply.github.com>
Co-authored-by: mohitc1 <22034758+mohitc1@users.noreply.github.com>
Co-authored-by: Mohit <mchand@microsoft.com>
Co-authored-by: pedro romero vargas <76129899+p3dr0rv@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: fadidurah <fadidurah@microsoft.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

No-Changelog This Pull-Request has no associated changelog entry.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants