Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions msal4j-mtls-extensions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# msal4j-mtls-extensions

This extension enables mTLS Proof-of-Possession (mTLS PoP) token acquisition for Azure Managed Identity in Java applications. It uses [JNA](https://github.com/java-native-access/jna) to call Windows CNG (`ncrypt.dll`) directly, creating and using KeyGuard-isolated private keys in-process — no .NET runtime or subprocess required.

The latest code resides in the `dev` branch.

Quick links:

| [Docs](docs/mtls-pop.md) | [Manual Testing](docs/mtls-pop-manual-testing.md) | [Architecture](docs/mtls-pop-architecture.md) | [Support](README.md#community-help-and-support) |
| --- | --- | --- | --- |

## Installation

### Requirements

- Windows x64 Azure VM with Managed Identity enabled
- Java 8 or higher
- `AttestationClientLib.dll` on `PATH`, when using Trusted Launch VMs with attestation (see [Attestation DLL](#attestationclientlibdll) below)

### Adding the dependency

```xml
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j-mtls-extensions</artifactId>
<version>1.0.0</version>
</dependency>
```

`msal4j-mtls-extensions` depends on `msal4j` transitively — you do not need to declare `msal4j` separately.

### AttestationClientLib.dll

On Trusted Launch VMs, the IMDS `/issuecredential` endpoint requires a MAA attestation JWT proving the key was created in a VBS-isolated enclave. This JWT is produced by `AttestationClientLib.dll`, distributed via the `Microsoft.Azure.Security.KeyGuardAttestation` NuGet package.

To obtain the DLL:

```powershell
# Download the NuGet package
dotnet add package Microsoft.Azure.Security.KeyGuardAttestation --package-directory C:\nuget

# Copy the DLL next to your application or to a directory on PATH
$dll = Get-ChildItem C:\nuget\microsoft.azure.security.keyguardattestation -Recurse -Filter "AttestationClientLib.dll" | Select-Object -First 1
Copy-Item $dll.FullName C:\your-app\
```

Unlike msal-dotnet, which receives this DLL automatically via NuGet, Java applications must place it on `PATH` or in the application directory. If your VM does not use Trusted Launch, pass `withAttestation: false` and no DLL is needed.

## Usage

Before using this extension, ensure Managed Identity is enabled on your Azure VM.

### Acquiring an mTLS PoP Token

Acquiring a token follows this general pattern:

1. Create a client and call `acquireToken()`.

* System-assigned Managed Identity:

```java
import com.microsoft.aad.msal4j.mtls.*;

MtlsMsiClient client = new MtlsMsiClient();
MtlsMsiHelperResult result = client.acquireToken(
"https://management.azure.com", // resource
"SystemAssigned", // identity type
null, // identity id (null for system-assigned)
false, // withAttestation — set true on Trusted Launch VMs
null // correlationId (optional)
);
String accessToken = result.getAccessToken();
```

* User-assigned Managed Identity:

```java
MtlsMsiHelperResult result = client.acquireToken(
"https://management.azure.com",
"UserAssigned",
"your-client-id",
false,
null
);
String accessToken = result.getAccessToken();
```

2. The binding certificate is cached in-process for the lifetime of the IMDS-issued certificate (minus a 5-minute safety margin). Subsequent calls return the cached token until it nears expiry.

### Making Downstream mTLS Calls

Once you have a token, use `httpRequest()` to make downstream calls over the same KeyGuard-backed mTLS channel:

```java
MtlsMsiHttpResponse response = client.httpRequest(
"https://myservice.example.com/api", // URL
"GET", // method
result.getAccessToken(), // bearer token
null, // body
null, // contentType
null, // extra headers
"https://management.azure.com", // resource (for cert refresh)
"SystemAssigned", null, // identity type, identity id
false, // withAttestation
null, // correlationId
false // allowInsecureTls
);
System.out.println(response.getStatus()); // e.g. 200
System.out.println(response.getBody());
```

The downstream server must be configured to *require* mutual TLS — it must send a TLS `CertificateRequest` during the handshake. Public Azure APIs (Graph, Key Vault, etc.) do not require a client certificate.

## Community Help and Support

We use [Stack Overflow](http://stackoverflow.com/questions/tagged/msal) to work with the community on supporting Azure Active Directory and its SDKs, including this one! We highly recommend you ask your questions on Stack Overflow (we're all on there!) Also browse existing issues to see if someone has had your question before. Please use the "msal" tag when asking your questions.

If you find a bug or have a feature request, please raise the issue on [GitHub Issues](https://github.com/AzureAD/microsoft-authentication-library-for-java/issues).

## Submit Feedback

We'd like your thoughts on this library. Please complete [this short survey.](https://forms.office.com/r/6AhHwQp3pe)

## Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

## Security Library

This library controls how users sign in and access services. We recommend you always take the latest version of our library in your app when possible. We use [semantic versioning](http://semver.org) so you can control the risk associated with updating your app. As an example, always downloading the latest minor version number (e.g. x.*y*.x) ensures you get the latest security and feature enhancements but our API surface remains the same. You can always see the latest version and release notes under the Releases tab of GitHub.

## Security Reporting

If you find a security issue with our libraries or services please report it to [secure@microsoft.com](mailto:secure@microsoft.com) with as much detail as possible. Your submission may be eligible for a bounty through the [Microsoft Bounty](http://aka.ms/bugbounty) program. Please do not post security issues to GitHub Issues or any other public site. We will contact you shortly upon receiving the information. We encourage you to get notifications of when security incidents occur by visiting [this page](https://technet.microsoft.com/en-us/security/dd252948) and subscribing to Security Advisory Alerts.

Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License (the "License").
85 changes: 85 additions & 0 deletions msal4j-mtls-extensions/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j-mtls-extensions</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>

<name>Microsoft Authentication Library for Java - mTLS Extensions</name>
<description>
Extension package that enables mTLS Proof-of-Possession (mTLS PoP) token acquisition
for Azure Managed Identity scenarios requiring KeyGuard-bound certificates. Uses JNA
to call Windows CNG (ncrypt.dll) and AttestationClientLib.dll directly from Java,
implementing a java.security.Provider that allows JSSE to use a non-exportable
KeyGuard RSA key during the TLS handshake. No .NET runtime or subprocess required.
</description>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>com.microsoft.azure</groupId>
<artifactId>msal4j</artifactId>
<version>1.23.1</version>
</dependency>

<!-- JNA: calls ncrypt.dll and AttestationClientLib.dll without a compiled JNI DLL -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.14.0</version>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<!-- Required for Mockito inline mock maker (static/final mocking) on Java 9+ -->
<argLine>
-XX:+EnableDynamicAgentLoading
--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.security=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.microsoft.aad.msal4j.mtls;

import com.sun.jna.Library;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.ptr.PointerByReference;

import java.util.Arrays;
import java.util.List;

/**
* JNA binding for {@code AttestationClientLib.dll} — the Windows DLL shipped by Azure
* that produces a MAA (Microsoft Azure Attestation) JWT proving a CNG KeyGuard key is
* hardware-protected.
*
* <p>Function signatures (ANSI cdecl, x64 Windows) documented in MSAL.NET's
* {@code KeyGuardMaa/AttestationInterop.cs} and also used by msal-go's
* {@code cng_windows.go}:</p>
* <pre>
* int InitAttestationLib(AttestationLogInfo*)
* int AttestKeyGuardImportKey(char* endpoint, char* authToken, char* clientPayload,
* NCRYPT_KEY_HANDLE keyHandle, char** token, char* clientId)
* void FreeAttestationToken(char* token)
* void UninitAttestationLib()
* </pre>
*
* <p>This interface is loaded lazily via {@link CngKeyGuard} — it is only required when
* MAA attestation is requested and the DLL is present on the system. If the DLL is absent
* and attestation is not requested, it is never loaded.</p>
*/
interface AttestationLibrary extends Library {

/**
* Mirrors the {@code AttestationLogInfo} struct:
* <pre>struct AttestationLogInfo { void* LogFunc; void* Ctx; }</pre>
* Pass zero values to disable logging.
*/
class AttestationLogInfo extends Structure {
/** Function pointer for the log callback. Use zero/null for no-op. */
public Pointer logFunc;
/** Caller context pointer, passed as first arg to logFunc. */
public Pointer ctx;

public AttestationLogInfo() {
logFunc = Pointer.NULL;
ctx = Pointer.NULL;
}

@Override
protected List<String> getFieldOrder() {
return Arrays.asList("logFunc", "ctx");
}
}

/**
* Initializes the attestation library.
*
* @param logInfo logging configuration; pass a zeroed struct to disable
* @return 0 on success, non-zero on failure
*/
int InitAttestationLib(AttestationLogInfo logInfo);

/**
* Produces a MAA JWT proving the given CNG key is VBS/KeyGuard-protected.
*
* @param endpoint MAA endpoint URL (ANSI string, e.g. "https://sharedcuse.cuse.attest.azure.net")
* @param authToken unused, pass null
* @param clientPayload unused, pass null
* @param keyHandle the {@code NCRYPT_KEY_HANDLE} from NCrypt* operations
* @param tokenOut receives the pointer to the MAA JWT string (caller must free with FreeAttestationToken)
* @param clientId managed identity client ID (ANSI string)
* @return 0 on success, non-zero on failure
*/
int AttestKeyGuardImportKey(String endpoint, String authToken, String clientPayload,
Pointer keyHandle, PointerByReference tokenOut, String clientId);

/**
* Frees a MAA JWT string allocated by {@link #AttestKeyGuardImportKey}.
*
* @param token the pointer returned in {@code tokenOut} by AttestKeyGuardImportKey
*/
void FreeAttestationToken(Pointer token);

/** Uninitializes the attestation library. Call after all attestation operations. */
void UninitAttestationLib();
}
Loading
Loading