Skip to content

Commit 6e1f6fa

Browse files
Merge branch 'main' into rginsburg/imds_probing
2 parents 4c2626e + 522d9ce commit 6e1f6fa

17 files changed

+96
-1118
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
4.79.2
2+
======
3+
4+
### Bug fixes
5+
* Bump winsdk dependency [#5575](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/5575)
6+
* ImdsV2 probe does not fire when .WithMtlsProofOfPossesstion is not used [#5579](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/5579)
7+
* Downgrade System.Formats.Asn1 to match ID web [#5583](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/5583)
8+
19
4.79.0
210
======
311

@@ -6,13 +14,15 @@
614
* Bearer Requests should Fallback to IMDS in Preview in https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/5562
715
* Updating MSAL to send client info = 2 on client credential flow in https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/5529
816
* Make `IMsalMtlsHttpClientFactory` interface public in https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/5559* Adjust WithExtraQueryParameters APIs and cache key behavior https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/5536
17+
* Adjust WithExtraQueryParameters APIs and cache key behavior [#5536](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/5536)
918

1019
### Bug fixes
1120
* Fix instance discovery bug in Fr cloud [#5549](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/5549)
1221
* Mark WithClientAssertion API as experimental [#5551](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/5551)
1322

1423

1524

25+
1626
4.78.0
1727
======
1828
### Changes

LibsAndSamples.sln

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 17
4-
VisualStudioVersion = 17.3.32708.82
3+
# Visual Studio Version 18
4+
VisualStudioVersion = 18.0.11217.181 d18.0
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9B0B5396-4D95-4C15-82ED-DC22B5A3123F}"
77
ProjectSection(SolutionItems) = preProject
@@ -117,8 +117,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Client.D
117117
EndProject
118118
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinFormsTestApp", "tests\devapps\WinFormsTestApp\WinFormsTestApp.csproj", "{F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}"
119119
EndProject
120-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KerberosConsole", "tests\devapps\KerberosConsole\KerberosConsole.csproj", "{94F35780-220A-4C08-83B9-41168F7017CD}"
121-
EndProject
122120
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Net5TestApp", "tests\devapps\Net5TestApp\Net5TestApp.csproj", "{998D38B3-344C-4F19-833E-6181B0834AF6}"
123121
EndProject
124122
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebApi", "WebApi", "{CC07F293-91B9-45A3-AA3A-77885BBCB624}"
@@ -1050,47 +1048,6 @@ Global
10501048
{F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Release|x64.Build.0 = Release|x64
10511049
{F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Release|x86.ActiveCfg = Release|x86
10521050
{F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Release|x86.Build.0 = Release|x86
1053-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|Any CPU.ActiveCfg = Debug + MobileApps|Any CPU
1054-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|Any CPU.Build.0 = Debug + MobileApps|Any CPU
1055-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|ARM.ActiveCfg = Debug + MobileApps|Any CPU
1056-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|ARM.Build.0 = Debug + MobileApps|Any CPU
1057-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|ARM64.ActiveCfg = Debug + MobileApps|Any CPU
1058-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|ARM64.Build.0 = Debug + MobileApps|Any CPU
1059-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|iPhone.ActiveCfg = Debug + MobileApps|Any CPU
1060-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|iPhone.Build.0 = Debug + MobileApps|Any CPU
1061-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|iPhoneSimulator.ActiveCfg = Debug + MobileApps|Any CPU
1062-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|iPhoneSimulator.Build.0 = Debug + MobileApps|Any CPU
1063-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|x64.ActiveCfg = Debug + MobileApps|Any CPU
1064-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|x64.Build.0 = Debug + MobileApps|Any CPU
1065-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|x86.ActiveCfg = Debug + MobileApps|Any CPU
1066-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|x86.Build.0 = Debug + MobileApps|Any CPU
1067-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1068-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
1069-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug|ARM.ActiveCfg = Debug|Any CPU
1070-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug|ARM.Build.0 = Debug|Any CPU
1071-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug|ARM64.ActiveCfg = Debug|Any CPU
1072-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug|ARM64.Build.0 = Debug|Any CPU
1073-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug|iPhone.ActiveCfg = Debug|Any CPU
1074-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug|iPhone.Build.0 = Debug|Any CPU
1075-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
1076-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
1077-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug|x64.ActiveCfg = Debug|Any CPU
1078-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug|x64.Build.0 = Debug|Any CPU
1079-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug|x86.ActiveCfg = Debug|Any CPU
1080-
{94F35780-220A-4C08-83B9-41168F7017CD}.Debug|x86.Build.0 = Debug|Any CPU
1081-
{94F35780-220A-4C08-83B9-41168F7017CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
1082-
{94F35780-220A-4C08-83B9-41168F7017CD}.Release|ARM.ActiveCfg = Release|Any CPU
1083-
{94F35780-220A-4C08-83B9-41168F7017CD}.Release|ARM.Build.0 = Release|Any CPU
1084-
{94F35780-220A-4C08-83B9-41168F7017CD}.Release|ARM64.ActiveCfg = Release|Any CPU
1085-
{94F35780-220A-4C08-83B9-41168F7017CD}.Release|ARM64.Build.0 = Release|Any CPU
1086-
{94F35780-220A-4C08-83B9-41168F7017CD}.Release|iPhone.ActiveCfg = Release|Any CPU
1087-
{94F35780-220A-4C08-83B9-41168F7017CD}.Release|iPhone.Build.0 = Release|Any CPU
1088-
{94F35780-220A-4C08-83B9-41168F7017CD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
1089-
{94F35780-220A-4C08-83B9-41168F7017CD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
1090-
{94F35780-220A-4C08-83B9-41168F7017CD}.Release|x64.ActiveCfg = Release|Any CPU
1091-
{94F35780-220A-4C08-83B9-41168F7017CD}.Release|x64.Build.0 = Release|Any CPU
1092-
{94F35780-220A-4C08-83B9-41168F7017CD}.Release|x86.ActiveCfg = Release|Any CPU
1093-
{94F35780-220A-4C08-83B9-41168F7017CD}.Release|x86.Build.0 = Release|Any CPU
10941051
{998D38B3-344C-4F19-833E-6181B0834AF6}.Debug + MobileApps|Any CPU.ActiveCfg = Debug + MobileApps|Any CPU
10951052
{998D38B3-344C-4F19-833E-6181B0834AF6}.Debug + MobileApps|Any CPU.Build.0 = Debug + MobileApps|Any CPU
10961053
{998D38B3-344C-4F19-833E-6181B0834AF6}.Debug + MobileApps|ARM.ActiveCfg = Debug + MobileApps|Any CPU
@@ -2062,7 +2019,6 @@ Global
20622019
{A7679FF0-19E8-41E3-9F7C-F54235124CC4} = {1A37FD75-94E9-4D6F-953A-0DABBD7B49E9}
20632020
{B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5} = {1A37FD75-94E9-4D6F-953A-0DABBD7B49E9}
20642021
{F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB}
2065-
{94F35780-220A-4C08-83B9-41168F7017CD} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB}
20662022
{998D38B3-344C-4F19-833E-6181B0834AF6} = {384BA371-F17F-4A70-9423-D54F71BB3FCB}
20672023
{CC07F293-91B9-45A3-AA3A-77885BBCB624} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB}
20682024
{DD23D8FF-86BA-4E9F-8AF1-0EBE0D86986A} = {CC07F293-91B9-45A3-AA3A-77885BBCB624}

docs/msi_v2/cert_handling.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Certificate Handling on Windows for MSI v2 / PoP
2+
3+
## Overview
4+
In the MSI v2 flow, the client must present a certificate over mTLS.
5+
The certificate itself is the **credential** for this flow.
6+
7+
Currently, the certificate is stored only in memory.
8+
To reduce repeated calls to IMDS and enable reuse across requests,
9+
the certificate should be **rooted (persisted)** in the Windows certificate store.
10+
11+
## 1. Certificate Store Location
12+
- Use the **Windows Certificate Store**.
13+
- Scope: **CurrentUser\My** store (per-user, not global).
14+
- This provides:
15+
- Secure key isolation (KeyGuard).
16+
- OS-enforced ACLs instead of manual file permissions.
17+
18+
---
19+
20+
## 2. Certificate Lifecycle
21+
22+
1. **Check Cert Store**
23+
- Look in `CurrentUser\My` for an MSI certificate.
24+
- Identify by subject and issuer.
25+
- If found and not expired → load it.
26+
27+
2. **No Valid Certificate**
28+
- Generate a key pair (RSA) in KeyGuard (for POP).
29+
- Get an attestation token for the key.
30+
- Create CSR in memory.
31+
- Call **IMDS issuecredential** endpoint.
32+
- Receive certificate from IMDS.
33+
34+
3. **Persist the Certificate**
35+
- Store the new certificate (with private key) in `CurrentUser\My`.
36+
- Mark the key as **non-exportable**. (uses KeyGuard keys - already non-exportable)
37+
38+
4. **Use Certificate**
39+
- Load cert from store as needed.
40+
- Use it for mTLS handshake.
41+
42+
---
43+
44+
## 3. Expiration and Renewal
45+
- Certificates are short-lived (7 days).
46+
- Always check expiration before use.
47+
- If expired or close to expiry:
48+
- Remove stale cert from store.
49+
- Acquire and persist a new cert from IMDS.
50+
51+
### Proactive Renewal: Start renewal at half the certificate lifetime (typically, 3.5 days) to avoid expiry during active sessions.
52+
53+
**This is calculated based on the certificate’s NotAfter property (expiration date).**
54+
---
55+
56+
## 4. Error Handling
57+
- If store entry is corrupt → remove it and fetch new cert.
58+
- If IMDS call fails → bubble up error to caller.
59+
60+
---
61+
62+
# Certificate Store Location in Linux
63+
- Rely on .NET’s X509Store abstractions for per-user certificate storage. The framework handles platform-specific details.”
64+
65+
---
66+
67+
## 5. Security Considerations
68+
- Private keys must be generated in **CNG/KeyGuard** with `ExportPolicy = None`.
69+
- Keys should **never** be exported or persisted outside the Windows certificate store.
70+
- `CurrentUser\My` is scoped to the signed-in user.
71+
72+
---
73+
74+
## ✅ Summary
75+
- Always **check Windows cert store first** (`CurrentUser\My`).
76+
- If no valid cert, **request one from IMDS**.
77+
- **Persist it** back into the store securely.
78+
- **Refresh on expiration**.

tests/Microsoft.Identity.Test.LabInfrastructure/CertificateHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public static X509Certificate2 FindCertificateByName(string subjectName)
3636
/// <param subjectName="location"><see cref="StoreLocation"/> in which to search for a matching certificate</param>
3737
/// <param subjectName="name"><see cref="StoreName"/> in which to search for a matching certificate</param>
3838
/// <returns><see cref="X509Certificate2"/> with <paramref subjectName="certName"/>, or null if no matching certificate was found</returns>
39-
public static X509Certificate2 FindCertificateByName(string certName, StoreLocation location, StoreName name)
39+
private static X509Certificate2 FindCertificateByName(string certName, StoreLocation location, StoreName name)
4040
{
4141
// Unix LocalMachine X509Store is limited to the Root and CertificateAuthority stores
4242
if (SharedUtilities.IsLinuxPlatform())

tests/Microsoft.Identity.Test.LabInfrastructure/KeyVaultConfiguration.cs

Lines changed: 0 additions & 38 deletions
This file was deleted.

tests/Microsoft.Identity.Test.LabInfrastructure/KeyVaultSecretsProvider.cs

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,33 +29,7 @@ public class KeyVaultSecretsProvider : IDisposable
2929
private CertificateClient _certificateClient;
3030
private SecretClient _secretClient;
3131

32-
/// <summary>Initialize the secrets provider with the "keyVault" configuration section.</summary>
33-
/// <remarks>
34-
/// <para>
35-
/// Authentication using <see cref="LabAccessAuthenticationType.ClientCertificate"/>
36-
/// 1. Register Azure AD application of "Web app / API" type.
37-
/// To set up certificate based access to the application PowerShell should be used.
38-
/// 2. Add an access policy entry to target Key Vault instance for this application.
39-
///
40-
/// The "keyVault" configuration section should define:
41-
/// "authType": "ClientCertificate"
42-
/// "clientId": [client ID]
43-
/// "certThumbprint": [certificate thumbprint]
44-
/// </para>
45-
/// <para>
46-
/// Authentication using <see cref="LabAccessAuthenticationType.UserCredential"/>
47-
/// 1. Register Azure AD application of "Native" type.
48-
/// 2. Add to 'Required permissions' access to 'Azure Key Vault (AzureKeyVault)' API.
49-
/// 3. When you run your native client application, it will automatically prompt user to enter Azure AD credentials.
50-
/// 4. To successfully access keys/secrets in the Key Vault, the user must have specific permissions to perform those operations.
51-
/// This could be achieved by directly adding an access policy entry to target Key Vault instance for this user
52-
/// or an access policy entry for an Azure AD security group of which this user is a member of.
53-
///
54-
/// The "keyVault" configuration section should define:
55-
/// "authType": "UserCredential"
56-
/// "clientId": [client ID]
57-
/// </para>
58-
/// </remarks>
32+
5933
public KeyVaultSecretsProvider(string keyVaultAddress = KeyVaultInstance.MSIDLab)
6034
{
6135
var credentials = GetKeyVaultCredentialAsync().GetAwaiter().GetResult();

tests/Microsoft.Identity.Test.LabInfrastructure/LabAuthenticationHelper.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,4 @@ public static async Task<AccessToken> GetLabAccessTokenAsync(string authority, s
7070
}
7171
}
7272

73-
public enum LabAccessAuthenticationType
74-
{
75-
ClientCertificate,
76-
ClientSecret,
77-
UserCredential
78-
}
7973
}

tests/Microsoft.Identity.Test.LabInfrastructure/LabResponse.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,12 @@ public class LabApp
2626
[JsonProperty("redirecturi")]
2727
public string RedirectUri { get; set; }
2828

29-
[JsonProperty("signinaudience")]
30-
public string Audience { get; set; }
31-
3229
// TODO: this is a list, but lab sends a string. Not used today, discuss with lab to return a list
3330
[JsonProperty("authority")]
3431
public string Authority { get; set; }
3532

3633
[JsonProperty("defaultscopes")]
3734
public string DefaultScopes { get; set; }
38-
3935
}
4036

4137
public class Lab
@@ -46,16 +42,7 @@ public class Lab
4642
[JsonProperty("federationprovider")]
4743
public FederationProvider FederationProvider { get; set; }
4844

49-
[JsonProperty("credentialvaultkeyname")]
50-
public string CredentialVaultkeyName { get; set; }
51-
5245
[JsonProperty("authority")]
5346
public string Authority { get; set; }
5447
}
55-
56-
public class LabCredentialResponse
57-
{
58-
[JsonProperty("Value")]
59-
public string Secret { get; set; }
60-
}
6148
}

tests/Microsoft.Identity.Test.LabInfrastructure/LabServiceApi.cs

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -75,35 +75,13 @@ private Task<string> RunQueryAsync(UserQuery query)
7575
if (string.IsNullOrEmpty(query.Upn))
7676
{
7777
//Building user query
78-
//Required parameters will be set to default if not supplied by the test code
79-
80-
queryDict.Add(
81-
LabApiConstants.MultiFactorAuthentication,
82-
query.MFA != null ?
83-
query.MFA.ToString() :
84-
MFA.None.ToString());
85-
86-
queryDict.Add(
87-
LabApiConstants.ProtectionPolicy,
88-
query.ProtectionPolicy != null ?
89-
query.ProtectionPolicy.ToString() :
90-
ProtectionPolicy.None.ToString());
91-
78+
//Required parameters will be set to default if not supplied by the test code
79+
9280
if (query.UserType != null)
9381
{
9482
queryDict.Add(LabApiConstants.UserType, query.UserType.ToString());
9583
}
96-
97-
if (query.HomeDomain != null)
98-
{
99-
queryDict.Add(LabApiConstants.HomeDomain, query.HomeDomain.ToString());
100-
}
101-
102-
if (query.HomeUPN != null)
103-
{
104-
queryDict.Add(LabApiConstants.HomeUPN, query.HomeUPN.ToString());
105-
}
106-
84+
10785
if (query.B2CIdentityProvider != null)
10886
{
10987
queryDict.Add(LabApiConstants.B2CProvider, query.B2CIdentityProvider.ToString());
@@ -168,17 +146,6 @@ internal async Task<string> GetLabResponseAsync(string address)
168146
}
169147
}
170148

171-
public async Task<string> GetUserSecretAsync(string lab)
172-
{
173-
Dictionary<string, string> queryDict = new Dictionary<string, string>
174-
{
175-
{ "secret", lab }
176-
};
177-
178-
string result = await SendLabRequestAsync(LabApiConstants.LabUserCredentialEndpoint, queryDict).ConfigureAwait(false);
179-
return JsonConvert.DeserializeObject<LabCredentialResponse>(result).Secret;
180-
}
181-
182149
public async Task<string> GetMSIHelperServiceTokenAsync()
183150
{
184151
if (_msiHelperApiAccessToken == null)

tests/Microsoft.Identity.Test.LabInfrastructure/LabUser.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,6 @@ public class LabUser
1818
[JsonProperty("upn")]
1919
public string Upn { get; set; }
2020

21-
[JsonProperty("displayname")]
22-
public string DisplayName { get; set; }
23-
24-
[JsonProperty("mfa")]
25-
public MFA Mfa { get; set; }
26-
27-
[JsonProperty("protectionpolicy")]
28-
public ProtectionPolicy ProtectionPolicy { get; set; }
29-
30-
[JsonProperty("homedomain")]
31-
public HomeDomain HomeDomain { get; set; }
32-
3321
[JsonProperty("homeupn")]
3422
public string HomeUPN { get; set; }
3523

@@ -41,8 +29,6 @@ public class LabUser
4129

4230
public FederationProvider FederationProvider { get; set; }
4331

44-
public string Credential { get; set; }
45-
4632
public string TenantId { get; set; }
4733

4834
private string _password = null;

0 commit comments

Comments
 (0)