diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md index 273768ef0ebc..c54656371a62 100644 --- a/sdk/identity/Azure.Identity/CHANGELOG.md +++ b/sdk/identity/Azure.Identity/CHANGELOG.md @@ -1,7 +1,9 @@ # Release History ## 1.3.0-preview.1 (Unreleased) +### New Features - Restoring Application Authentication APIs from 1.2.0-preview.6 +- Added `IncludeX5CClaimHeader` to `ClientCertificateCredentialOptions` to enable subject name / issuer authentication with the `ClientCertificateCredential`. ### Fixes and improvements - Fixed issue with non GUID Client Ids (Issue [#14585](https://github.com/Azure/azure-sdk-for-net/issues/14585)) diff --git a/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs b/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs index 5f2bbd791aab..a1696148f5fd 100644 --- a/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs +++ b/sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs @@ -68,6 +68,7 @@ public partial class ClientCertificateCredentialOptions : Azure.Identity.TokenCr public ClientCertificateCredentialOptions() { } public bool AllowUnencryptedCache { get { throw null; } set { } } public bool EnablePersistentCache { get { throw null; } set { } } + public bool IncludeX5CCliamHeader { get { throw null; } set { } } } public partial class ClientSecretCredential : Azure.Core.TokenCredential { diff --git a/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs b/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs index 2643b7ba4a6c..803444cb965a 100644 --- a/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs +++ b/sdk/identity/Azure.Identity/src/ClientCertificateCredential.cs @@ -133,7 +133,7 @@ internal ClientCertificateCredential(string tenantId, string clientId, IX509Cert _pipeline = pipeline ?? CredentialPipeline.GetInstance(options); - _client = client ?? new MsalConfidentialClient(_pipeline, tenantId, clientId, certificateProvider, options as ITokenCacheOptions); + _client = client ?? new MsalConfidentialClient(_pipeline, tenantId, clientId, certificateProvider, (options as ClientCertificateCredentialOptions)?.IncludeX5CCliamHeader ?? false, options as ITokenCacheOptions); } /// diff --git a/sdk/identity/Azure.Identity/src/ClientCertificateCredentialOptions.cs b/sdk/identity/Azure.Identity/src/ClientCertificateCredentialOptions.cs index 5899c46b1991..94453e7e3042 100644 --- a/sdk/identity/Azure.Identity/src/ClientCertificateCredentialOptions.cs +++ b/sdk/identity/Azure.Identity/src/ClientCertificateCredentialOptions.cs @@ -18,5 +18,10 @@ public class ClientCertificateCredentialOptions : TokenCredentialOptions, IToken /// If set to true the credential will fall back to storing tokens in an unencrypted file if no OS level user encryption is available. /// public bool AllowUnencryptedCache { get; set; } + + /// + /// Will include x5c header to enable subject name / issuer based authentication for the . + /// + public bool IncludeX5CCliamHeader { get; set; } } } diff --git a/sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs b/sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs index 86193b2a7c34..ba070d65f90c 100644 --- a/sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs +++ b/sdk/identity/Azure.Identity/src/MsalConfidentialClient.cs @@ -13,6 +13,7 @@ namespace Azure.Identity internal class MsalConfidentialClient : MsalClientBase { private readonly string _clientSecret; + private readonly bool _includeX5CClaimHeader; private readonly ClientCertificateCredential.IX509Certificate2Provider _certificateProvider; /// @@ -29,9 +30,10 @@ public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, stri _clientSecret = clientSecret; } - public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, ClientCertificateCredential.IX509Certificate2Provider certificateProvider, ITokenCacheOptions cacheOptions) + public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, string clientId, ClientCertificateCredential.IX509Certificate2Provider certificateProvider, bool includeX5CClaimHeader, ITokenCacheOptions cacheOptions) : base(pipeline, tenantId, clientId, cacheOptions) { + _includeX5CClaimHeader = includeX5CClaimHeader; _certificateProvider = certificateProvider; } @@ -56,7 +58,8 @@ protected override async ValueTask CreateClientA public virtual async ValueTask AcquireTokenForClientAsync(string[] scopes, bool async, CancellationToken cancellationToken) { IConfidentialClientApplication client = await GetClientAsync(async, cancellationToken).ConfigureAwait(false); - return await client.AcquireTokenForClient(scopes).ExecuteAsync(async, cancellationToken).ConfigureAwait(false); + + return await client.AcquireTokenForClient(scopes).WithSendX5C(_includeX5CClaimHeader).ExecuteAsync(async, cancellationToken).ConfigureAwait(false); } } } diff --git a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialLiveTests.cs b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialLiveTests.cs index df61b67fb945..5e58cac944f8 100644 --- a/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialLiveTests.cs +++ b/sdk/identity/Azure.Identity/tests/ClientCertificateCredentialLiveTests.cs @@ -101,6 +101,25 @@ public async Task FromX509Certificate2() } } + [Test] + public async Task IncludeX5CCliamHeader() + { + var tenantId = TestEnvironment.ServicePrincipalTenantId; + var clientId = TestEnvironment.ServicePrincipalClientId; + var certPath = TestEnvironment.ServicePrincipalSniCertificatePath; + + var options = Recording.InstrumentClientOptions(new ClientCertificateCredentialOptions { IncludeX5CCliamHeader = true }); + + var credential = new ClientCertificateCredential(tenantId, clientId, certPath, options); + + var tokenRequestContext = new TokenRequestContext(new[] { AzureAuthorityHosts.GetDefaultScope(AzureAuthorityHosts.AzurePublicCloud) }); + + // ensure we can initially acquire a token + AccessToken token = await credential.GetTokenAsync(tokenRequestContext); + + Assert.IsNotNull(token.Token); + } + [Test] public void IncorrectCertificate() { diff --git a/sdk/identity/Azure.Identity/tests/IdentityTestEnvironment.cs b/sdk/identity/Azure.Identity/tests/IdentityTestEnvironment.cs index 30619c7b6c20..e92328b2f3ec 100644 --- a/sdk/identity/Azure.Identity/tests/IdentityTestEnvironment.cs +++ b/sdk/identity/Azure.Identity/tests/IdentityTestEnvironment.cs @@ -29,6 +29,6 @@ public IdentityTestEnvironment() : base("identity") public string ServicePrincipalClientSecret => GetOptionalVariable("IDENTITY_SP_CLIENT_SECRET") ?? "SANITIZED"; public string ServicePrincipalCertificatePfxPath => GetOptionalVariable("IDENTITY_SP_CERT_PFX") ?? Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pfx"); public string ServicePrincipalCertificatePemPath => GetOptionalVariable("IDENTITY_SP_CERT_PEM") ?? Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pem"); - + public string ServicePrincipalSniCertificatePath => GetOptionalVariable("IDENTITY_SP_SNI_CERT") ?? Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "cert.pfx"); } } diff --git a/sdk/identity/Azure.Identity/tests/SessionRecords/ClientCertificateCredentialLiveTests/IncludeX5CCliamHeader.json b/sdk/identity/Azure.Identity/tests/SessionRecords/ClientCertificateCredentialLiveTests/IncludeX5CCliamHeader.json new file mode 100644 index 000000000000..33e512b9d677 --- /dev/null +++ b/sdk/identity/Azure.Identity/tests/SessionRecords/ClientCertificateCredentialLiveTests/IncludeX5CCliamHeader.json @@ -0,0 +1,149 @@ +{ + "Entries": [ + { + "RequestUri": "https://login.microsoftonline.com/common/discovery/instance?api-version=1.1\u0026authorization_endpoint=https%3A%2F%2Flogin.microsoftonline.com%2F72f988bf-86f1-41af-91ab-2d7cd011db47%2Foauth2%2Fv2.0%2Fauthorize", + "RequestMethod": "GET", + "RequestHeaders": { + "client-request-id": "e0b80d1e-5fe3-4a2e-851e-939a778c3df9", + "return-client-request-id": "true", + "User-Agent": [ + "azsdk-net-Identity/1.3.0-dev.20200827.1", + "(.NET Core 4.6.29130.01; Microsoft Windows 10.0.19042 )" + ], + "x-app-name": "UnknownClient", + "x-app-ver": "0.0.0.0", + "x-client-OS": "Microsoft Windows 10.0.19042 ", + "x-client-SKU": "MSAL.NetCore", + "x-client-Ver": "4.16.1.0", + "x-ms-client-request-id": "7cc6c20a26114afde4cf881ebd9cd9db", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Origin": "*", + "Cache-Control": "max-age=86400, private", + "client-request-id": "e0b80d1e-5fe3-4a2e-851e-939a778c3df9", + "Content-Length": "980", + "Content-Type": "application/json; charset=utf-8", + "Date": "Fri, 28 Aug 2020 00:13:37 GMT", + "P3P": "CP=\u0022DSP CUR OTPi IND OTRi ONL FIN\u0022", + "Set-Cookie": [ + "fpc=AlGY5STXW5ZLq9Rr0moIFCk; expires=Sun, 27-Sep-2020 00:13:37 GMT; path=/; secure; HttpOnly; SameSite=None", + "esctx=AQABAAAAAAAGV_bv21oQQ4ROqh0_1-tAcWo8Q3Ao1APL8uJlLLFWyqTuCs8MaT-wJ2cQmupmBVsbR3qKuxSDGX75k3GaApwtaLsTziP8ma9gCJ6n8B9qYXeMDmwJmLCMSVAyfqCus5CirUVo-u6S87AQ-eMMMJDUIoxMVCt8fl73zRZ06gZYg0BDk5GRUF_QLAVLr23EqcsgAA; domain=.login.microsoftonline.com; path=/; secure; HttpOnly; SameSite=None", + "x-ms-gateway-slice=prod; path=/; secure; samesite=none; httponly", + "stsservicecookie=ests; path=/; secure; samesite=none; httponly" + ], + "Strict-Transport-Security": "max-age=31536000; includeSubDomains", + "X-Content-Type-Options": "nosniff", + "x-ms-ests-server": "2.1.10985.8 - WUS2 ProdSlices", + "x-ms-request-id": "5cb10198-5ec3-4184-b78a-09af764d7500" + }, + "ResponseBody": { + "tenant_discovery_endpoint": "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/v2.0/.well-known/openid-configuration", + "api-version": "1.1", + "metadata": [ + { + "preferred_network": "login.microsoftonline.com", + "preferred_cache": "login.windows.net", + "aliases": [ + "login.microsoftonline.com", + "login.windows.net", + "login.microsoft.com", + "sts.windows.net" + ] + }, + { + "preferred_network": "login.partner.microsoftonline.cn", + "preferred_cache": "login.partner.microsoftonline.cn", + "aliases": [ + "login.partner.microsoftonline.cn", + "login.chinacloudapi.cn" + ] + }, + { + "preferred_network": "login.microsoftonline.de", + "preferred_cache": "login.microsoftonline.de", + "aliases": [ + "login.microsoftonline.de" + ] + }, + { + "preferred_network": "login.microsoftonline.us", + "preferred_cache": "login.microsoftonline.us", + "aliases": [ + "login.microsoftonline.us", + "login.usgovcloudapi.net" + ] + }, + { + "preferred_network": "login-us.microsoftonline.com", + "preferred_cache": "login-us.microsoftonline.com", + "aliases": [ + "login-us.microsoftonline.com" + ] + } + ] + } + }, + { + "RequestUri": "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/v2.0/token", + "RequestMethod": "POST", + "RequestHeaders": { + "client-request-id": "e0b80d1e-5fe3-4a2e-851e-939a778c3df9", + "Content-Length": "9", + "return-client-request-id": "true", + "User-Agent": [ + "azsdk-net-Identity/1.3.0-dev.20200827.1", + "(.NET Core 4.6.29130.01; Microsoft Windows 10.0.19042 )" + ], + "x-app-name": "UnknownClient", + "x-app-ver": "0.0.0.0", + "x-client-current-telemetry": "2|1004,0|", + "x-client-last-telemetry": "2|0|||", + "x-client-OS": "Microsoft Windows 10.0.19042 ", + "x-client-SKU": "MSAL.NetCore", + "x-client-Ver": "4.16.1.0", + "x-ms-client-request-id": "61628e6c00ac8678b3611b73baecfae0", + "x-ms-lib-capability": "retry-after, h429", + "x-ms-PKeyAuth": "1.0", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": "U2FuaXRpemVk", + "StatusCode": 200, + "ResponseHeaders": { + "Cache-Control": "no-store, no-cache", + "client-request-id": "e0b80d1e-5fe3-4a2e-851e-939a778c3df9", + "Content-Length": "111", + "Content-Type": "application/json; charset=utf-8", + "Date": "Fri, 28 Aug 2020 00:13:37 GMT", + "Expires": "-1", + "P3P": "CP=\u0022DSP CUR OTPi IND OTRi ONL FIN\u0022", + "Pragma": "no-cache", + "Set-Cookie": [ + "fpc=AlGY5STXW5ZLq9Rr0moIFCmfc-ozAQAAADFD2tYOAAAA; expires=Sun, 27-Sep-2020 00:13:38 GMT; path=/; secure; HttpOnly; SameSite=None", + "x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly", + "stsservicecookie=ests; path=/; secure; samesite=none; httponly" + ], + "Strict-Transport-Security": "max-age=31536000; includeSubDomains", + "X-Content-Type-Options": "nosniff", + "x-ms-clitelem": "1,0,0,,", + "x-ms-ests-server": "2.1.10985.8 - WUS2 ProdSlices", + "x-ms-request-id": "e08e1290-d332-4f57-bbb1-7ef7bbff0900" + }, + "ResponseBody": { + "token_type": "Bearer", + "expires_in": 86399, + "ext_expires_in": 86399, + "refresh_in": 43199, + "access_token": "Sanitized" + } + } + ], + "Variables": { + "IDENTITY_SP_CLIENT_ID": "2c36e5bd-85e1-49c4-95d4-ab070acfb606", + "IDENTITY_SP_TENANT_ID": "72f988bf-86f1-41af-91ab-2d7cd011db47", + "RandomSeed": "572638946" + } +} \ No newline at end of file diff --git a/sdk/identity/Azure.Identity/tests/SessionRecords/ClientCertificateCredentialLiveTests/IncludeX5CCliamHeaderAsync.json b/sdk/identity/Azure.Identity/tests/SessionRecords/ClientCertificateCredentialLiveTests/IncludeX5CCliamHeaderAsync.json new file mode 100644 index 000000000000..143c275974a0 --- /dev/null +++ b/sdk/identity/Azure.Identity/tests/SessionRecords/ClientCertificateCredentialLiveTests/IncludeX5CCliamHeaderAsync.json @@ -0,0 +1,148 @@ +{ + "Entries": [ + { + "RequestUri": "https://login.microsoftonline.com/common/discovery/instance?api-version=1.1\u0026authorization_endpoint=https%3A%2F%2Flogin.microsoftonline.com%2F72f988bf-86f1-41af-91ab-2d7cd011db47%2Foauth2%2Fv2.0%2Fauthorize", + "RequestMethod": "GET", + "RequestHeaders": { + "client-request-id": "b70fccea-27b9-4715-b556-11c49a362db0", + "return-client-request-id": "true", + "User-Agent": [ + "azsdk-net-Identity/1.3.0-dev.20200827.1", + "(.NET Core 4.6.29130.01; Microsoft Windows 10.0.19042 )" + ], + "x-app-name": "UnknownClient", + "x-app-ver": "0.0.0.0", + "x-client-OS": "Microsoft Windows 10.0.19042 ", + "x-client-SKU": "MSAL.NetCore", + "x-client-Ver": "4.16.1.0", + "x-ms-client-request-id": "75d684d87e8c13dc5f0f103bb42ceeba", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": null, + "StatusCode": 200, + "ResponseHeaders": { + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Origin": "*", + "Cache-Control": "max-age=86400, private", + "client-request-id": "b70fccea-27b9-4715-b556-11c49a362db0", + "Content-Length": "980", + "Content-Type": "application/json; charset=utf-8", + "Date": "Fri, 28 Aug 2020 00:13:38 GMT", + "P3P": "CP=\u0022DSP CUR OTPi IND OTRi ONL FIN\u0022", + "Set-Cookie": [ + "fpc=AlGY5STXW5ZLq9Rr0moIFCmfc-ozAQAAADFD2tYOAAAA; expires=Sun, 27-Sep-2020 00:13:38 GMT; path=/; secure; HttpOnly; SameSite=None", + "x-ms-gateway-slice=prod; path=/; secure; samesite=none; httponly", + "stsservicecookie=ests; path=/; secure; samesite=none; httponly" + ], + "Strict-Transport-Security": "max-age=31536000; includeSubDomains", + "X-Content-Type-Options": "nosniff", + "x-ms-ests-server": "2.1.10985.8 - WUS2 ProdSlices", + "x-ms-request-id": "d36a690f-f710-40f5-815c-33b26abbbc00" + }, + "ResponseBody": { + "tenant_discovery_endpoint": "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/v2.0/.well-known/openid-configuration", + "api-version": "1.1", + "metadata": [ + { + "preferred_network": "login.microsoftonline.com", + "preferred_cache": "login.windows.net", + "aliases": [ + "login.microsoftonline.com", + "login.windows.net", + "login.microsoft.com", + "sts.windows.net" + ] + }, + { + "preferred_network": "login.partner.microsoftonline.cn", + "preferred_cache": "login.partner.microsoftonline.cn", + "aliases": [ + "login.partner.microsoftonline.cn", + "login.chinacloudapi.cn" + ] + }, + { + "preferred_network": "login.microsoftonline.de", + "preferred_cache": "login.microsoftonline.de", + "aliases": [ + "login.microsoftonline.de" + ] + }, + { + "preferred_network": "login.microsoftonline.us", + "preferred_cache": "login.microsoftonline.us", + "aliases": [ + "login.microsoftonline.us", + "login.usgovcloudapi.net" + ] + }, + { + "preferred_network": "login-us.microsoftonline.com", + "preferred_cache": "login-us.microsoftonline.com", + "aliases": [ + "login-us.microsoftonline.com" + ] + } + ] + } + }, + { + "RequestUri": "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/v2.0/token", + "RequestMethod": "POST", + "RequestHeaders": { + "client-request-id": "b70fccea-27b9-4715-b556-11c49a362db0", + "Content-Length": "9", + "return-client-request-id": "true", + "User-Agent": [ + "azsdk-net-Identity/1.3.0-dev.20200827.1", + "(.NET Core 4.6.29130.01; Microsoft Windows 10.0.19042 )" + ], + "x-app-name": "UnknownClient", + "x-app-ver": "0.0.0.0", + "x-client-current-telemetry": "2|1004,0|", + "x-client-last-telemetry": "2|0|||", + "x-client-OS": "Microsoft Windows 10.0.19042 ", + "x-client-SKU": "MSAL.NetCore", + "x-client-Ver": "4.16.1.0", + "x-ms-client-request-id": "03d87b7feafd684b36bc15c68c35ffa6", + "x-ms-lib-capability": "retry-after, h429", + "x-ms-PKeyAuth": "1.0", + "x-ms-return-client-request-id": "true" + }, + "RequestBody": "U2FuaXRpemVk", + "StatusCode": 200, + "ResponseHeaders": { + "Cache-Control": "no-store, no-cache", + "client-request-id": "b70fccea-27b9-4715-b556-11c49a362db0", + "Content-Length": "111", + "Content-Type": "application/json; charset=utf-8", + "Date": "Fri, 28 Aug 2020 00:13:38 GMT", + "Expires": "-1", + "P3P": "CP=\u0022DSP CUR OTPi IND OTRi ONL FIN\u0022", + "Pragma": "no-cache", + "Set-Cookie": [ + "fpc=AlGY5STXW5ZLq9Rr0moIFCmfc-ozAgAAADFD2tYOAAAA; expires=Sun, 27-Sep-2020 00:13:38 GMT; path=/; secure; HttpOnly; SameSite=None", + "x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly", + "stsservicecookie=ests; path=/; secure; samesite=none; httponly" + ], + "Strict-Transport-Security": "max-age=31536000; includeSubDomains", + "X-Content-Type-Options": "nosniff", + "x-ms-clitelem": "1,0,0,,", + "x-ms-ests-server": "2.1.10985.8 - WUS2 ProdSlices", + "x-ms-request-id": "e08e1290-d332-4f57-bbb1-7ef7ceff0900" + }, + "ResponseBody": { + "token_type": "Bearer", + "expires_in": 86399, + "ext_expires_in": 86399, + "refresh_in": 43199, + "access_token": "Sanitized" + } + } + ], + "Variables": { + "IDENTITY_SP_CLIENT_ID": "2c36e5bd-85e1-49c4-95d4-ab070acfb606", + "IDENTITY_SP_TENANT_ID": "72f988bf-86f1-41af-91ab-2d7cd011db47", + "RandomSeed": "817953015" + } +} \ No newline at end of file diff --git a/sdk/identity/tests.yml b/sdk/identity/tests.yml index c168281251a9..74eac9862bf5 100644 --- a/sdk/identity/tests.yml +++ b/sdk/identity/tests.yml @@ -24,9 +24,11 @@ extends: - pwsh: | [System.Convert]::FromBase64String($env:PFX_CONTENTS) | Set-Content -Path $(Agent.TempDirectory)/test.pfx -AsByteStream Set-Content -Path $(Agent.TempDirectory)/test.pem -Value $env:PEM_CONTENTS + [System.Convert]::FromBase64String($env:SNI_CONTENTS) | Set-Content -Path $(Agent.TempDirectory)/testsni.pfx -AsByteStream env: PFX_CONTENTS: $(net-identity-spcert-pfx) PEM_CONTENTS: $(net-identity-spcert-pem) + SNI_CONTENTS: $(net-identity-spcert-sni) EnvVars: AZURE_IDENTITY_TEST_TENANTID: $(net-identity-tenantid) AZURE_IDENTITY_TEST_USERNAME: $(net-identity-username) @@ -36,3 +38,4 @@ extends: IDENTITY_SP_CLIENT_SECRET: $(net-identity-sp-clientsecret) IDENTITY_SP_CERT_PFX: $(Agent.TempDirectory)/test.pfx IDENTITY_SP_CERT_PEM: $(Agent.TempDirectory)/test.pem + IDENTITY_SP_CERT_SNI: $(Agent.TempDirectory)/testsni.pfx