diff --git a/README.md b/README.md index b178e39..a897a27 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ direction TB <<rw>> +string CertificateDistinguishedName <<rw>> +string KeyVaultCertificateName <<rw>> +string CertificateThumbprint + <<rw>> +string CertificateSubjectName <<rw>> +string CertificateDiskPath <<rw>> +string CertificatePassword <<rw>> +string Base64EncodedValue @@ -134,6 +135,8 @@ direction TB SignedAssertionFromVault = 9 AutoDecryptKeys = 10 CustomSignedAssertion = 11 + ManagedCertificate = 12 + StoreWithSubjectName = 13 } class CredentialType { <> Certificate = 0 @@ -298,6 +301,7 @@ Note: <<rw>> +string KeyVaultUrl <<rw>> +string CertificateStorePath <<rw>> +string CertificateDistinguishedName + <<rw>> +string CertificateSubjectName <<rw>> +string KeyVaultCertificateName <<rw>> +string CertificateThumbprint <<rw>> +string CertificateDiskPath @@ -330,6 +334,8 @@ Note: SignedAssertionFromVault = 9 AutoDecryptKeys = 10 CustomSignedAssertion = 11 + ManagedCertificate = 12 + StoreWithSubjectName = 13 } class CredentialType { <> Certificate = 0 diff --git a/docs/credentialdescription.md b/docs/credentialdescription.md index a7f6a5b..df45e6f 100644 --- a/docs/credentialdescription.md +++ b/docs/credentialdescription.md @@ -56,7 +56,7 @@ This is the recommended approach |----------------|------------|-------------|------------|----------------| | **Federation identity credential with Managed Identity (FIC+MSI)**
(`SignedAssertionFromManagedIdentity`) | Azure Managed Identity | • Production on Azure
• Zero cert management
• Cloud-native apps

_System-assigned_: Resource lifecycle
_User-assigned_: Share across resources | • No secret management
• Automatic rotation
• Azure integration
• Enhanced security | Best choice for Azure workloads | | **Key Vault**
(`SourceType = KeyVault`) | Certificate stored in Azure Key Vault | • Production environments
• Need for centralized management
• Require automatic rotation
• Share across services | • Secure storage & access control
• Automatic renewal
• Audit logging
• Managed backup/recovery | Best choice for production workloads not hosted on an Azure compute supporting managed identity| -| **Certificate Store**
(`StoreWithThumbprint` or `StoreWithDistinguishedName`) | Certificate in Windows Certificate Store | • Production on Windows
• Using Windows cert management

_Thumbprint_: Target specific version
_DistinguishedName_: Auto rollover | • Native Windows management
• Windows security integration
• HSM support | Ideal for Windows production environments | +| **Certificate Store**
(`StoreWithThumbprint` or `StoreWithDistinguishedName` or `StoreWithSubjectName`) | Certificate in Windows Certificate Store | • Production on Windows
• Using Windows cert management

_Thumbprint_: Target specific version
_DistinguishedName_: Auto rollover
_SubjectName_: Flexible name-based lookup | • Native Windows management
• Windows security integration
• HSM support | Ideal for Windows production environments | | **File Path**
(`SourceType = Path`) | PFX/P12 file on disk | • Development/testing
• Simple deployment
• File-based config preferred | • Simple setup
• Easy deployment
• Direct file access | **Not for production:**
• Password in config
• Manual management
• Less secure storage | | **Base64 Encoded**
(`SourceType = Base64Encoded`) | Certificate as base64 string | • Development/testing
• Config-embedded certificates | • Simple configuration
• No file system dependency | **Not for production:**
• Exposed in config
• Manual management
• Less secure | | **Client Secret**
(`SourceType = ClientSecret`) | Simple shared secret string | • Development/testing
• Basic security requirements | • Simple to use
• Easy to configure | **Not for production:**
• Less secure
• No auto-rotation
• Easy to expose | @@ -119,6 +119,18 @@ This is the case where the client credentials are a certificate. } ``` +#### From Certificate Store (Using Subject Name) +```json +{ + "ClientCredentials": [ + { + "SourceType": "StoreWithSubjectName", + "CertificateStorePath": "CurrentUser/My", + "CertificateSubjectName": "CN=WebAppCallingWebApiCert" + }] +} +``` + #### From File Path ```json { @@ -234,6 +246,17 @@ var credentialDescription = CredentialDescription.FromCertificateStore( distinguishedName: "CN=WebAppCallingWebApiCert"); ``` +#### From Certificate Store (Using Subject Name) +```csharp +// Using property initialization +var credentialDescription = new CredentialDescription +{ + SourceType = CredentialSource.StoreWithSubjectName, + CertificateStorePath = "LocalMachine/My", + CertificateSubjectName = "CN=WebAppCallingWebApiCert" +}; +``` + #### From File Path ```csharp // Using property initialization @@ -335,6 +358,7 @@ var credentialDescription = new CredentialDescription - Common values: - `CurrentUser/My`: User certificates - `LocalMachine/My`: Computer certificates + - Used with `StoreWithThumbprint`, `StoreWithDistinguishedName`, and `StoreWithSubjectName` 4. **Federation Identity Credential with Managed Identity**: - For system-assigned managed identity, use `SignedAssertionFromManagedIdentity` source type without specifying `ManagedIdentityClientId` diff --git a/src/Microsoft.Identity.Abstractions/ApplicationOptions/CredentialDescription.cs b/src/Microsoft.Identity.Abstractions/ApplicationOptions/CredentialDescription.cs index 27d64de..5a78c2c 100644 --- a/src/Microsoft.Identity.Abstractions/ApplicationOptions/CredentialDescription.cs +++ b/src/Microsoft.Identity.Abstractions/ApplicationOptions/CredentialDescription.cs @@ -47,6 +47,7 @@ public CredentialDescription(CredentialDescription other) _certificate = other._certificate; CertificateStorePath = other.CertificateStorePath; CertificateDistinguishedName = other.CertificateDistinguishedName; + CertificateSubjectName = other.CertificateSubjectName; CertificateThumbprint = other.CertificateThumbprint; CertificateDiskPath = other.CertificateDiskPath; CertificatePassword = other.CertificatePassword; @@ -108,6 +109,9 @@ public string Id case CredentialSource.StoreWithDistinguishedName: _cachedId = $"CertificateStoreWithDistinguishedName={CertificateStorePath}/{CertificateDistinguishedName};Thumbprint={certificateThumbprint}"; break; + case CredentialSource.StoreWithSubjectName: + _cachedId = $"CertificateStoreWithSubjectName={CertificateStorePath}/{CertificateSubjectName};Thumbprint={certificateThumbprint}"; + break; case CredentialSource.ClientSecret: _cachedId = $"RedactedClientSecret={GenerateHash(ClientSecret)}"; break; @@ -203,11 +207,11 @@ public string? KeyVaultUrl /// /// When is or - /// , specifies the certificate store from which to extract + /// or , specifies the certificate store from which to extract /// the certificate. The format is the concatenation of a value of and a value of /// separated by a slash. For instance, use CurrentUser/My for a user certificate, and LocalMachine/My for a computer certificate. /// - /// Use this property in conjunction with or . + /// Use this property in conjunction with , , or . /// /// /// @@ -251,6 +255,33 @@ public string? CertificateDistinguishedName } private string? _certificateDistinguishedName; + /// + /// When is , specifies the subject name of + /// the certificate in the store specified by . + /// + /// + /// + /// CurrentUser/My) and specified by its subject name, used as a client credential in a confidential client application: + /// :::code language="json" source="~/../abstractions-samples/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionTest.cs" id="subjectname_json"::: + /// + /// The code below describes programmatically in C#, a computer certificate in the personal certificates folder (LocalMachine/My) accessed by its subject name. + /// :::code language="csharp" source="~/../abstractions-samples/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionTest.cs" id="subjectname_csharp"::: + /// ]]> + /// + /// + /// + public string? CertificateSubjectName + { + get => _certificateSubjectName; + set + { + _certificateSubjectName = value; + _cachedId = null; + } + } + private string? _certificateSubjectName; + /// /// When is , use this property to specify the /// the name of the certificate in Key Vault in conjunction with . @@ -595,7 +626,8 @@ public virtual object? CachedValue /// /// when is , you will use this property to provide the certificate yourself. /// When is or - /// or or or + /// or or or + /// or /// /// /// @@ -622,6 +654,7 @@ public CredentialType CredentialType or CredentialSource.Path or CredentialSource.StoreWithThumbprint or CredentialSource.StoreWithDistinguishedName + or CredentialSource.StoreWithSubjectName or CredentialSource.Certificate or CredentialSource.Base64Encoded or CredentialSource.ManagedCertificate => CredentialType.Certificate, diff --git a/src/Microsoft.Identity.Abstractions/ApplicationOptions/CredentialDescriptionJsonConverter.cs b/src/Microsoft.Identity.Abstractions/ApplicationOptions/CredentialDescriptionJsonConverter.cs index 228c6b4..db8b632 100644 --- a/src/Microsoft.Identity.Abstractions/ApplicationOptions/CredentialDescriptionJsonConverter.cs +++ b/src/Microsoft.Identity.Abstractions/ApplicationOptions/CredentialDescriptionJsonConverter.cs @@ -56,6 +56,9 @@ public override CredentialDescription Read(ref Utf8JsonReader reader, Type typeT case string _ when propertyName.Equals(nameof(CredentialDescription.CertificateDistinguishedName), StringComparison.OrdinalIgnoreCase): credentialDescription.CertificateDistinguishedName = reader.GetString(); break; + case string _ when propertyName.Equals(nameof(CredentialDescription.CertificateSubjectName), StringComparison.OrdinalIgnoreCase): + credentialDescription.CertificateSubjectName = reader.GetString(); + break; case string _ when propertyName.Equals(nameof(CredentialDescription.CertificateThumbprint), StringComparison.OrdinalIgnoreCase): credentialDescription.CertificateThumbprint = reader.GetString(); break; @@ -144,6 +147,13 @@ public override void Write(Utf8JsonWriter writer, CredentialDescription value, J writer.WriteString(nameof(CredentialDescription.CertificateDistinguishedName), value.CertificateDistinguishedName); break; + case CredentialSource.StoreWithSubjectName: + if (!string.IsNullOrEmpty(value.CertificateStorePath)) + writer.WriteString(nameof(CredentialDescription.CertificateStorePath), value.CertificateStorePath); + if (!string.IsNullOrEmpty(value.CertificateSubjectName)) + writer.WriteString(nameof(CredentialDescription.CertificateSubjectName), value.CertificateSubjectName); + break; + case CredentialSource.KeyVault: if (!string.IsNullOrEmpty(value.KeyVaultUrl)) writer.WriteString(nameof(CredentialDescription.KeyVaultUrl), value.KeyVaultUrl); diff --git a/src/Microsoft.Identity.Abstractions/ApplicationOptions/CredentialSource.cs b/src/Microsoft.Identity.Abstractions/ApplicationOptions/CredentialSource.cs index 52d50c7..5721aad 100644 --- a/src/Microsoft.Identity.Abstractions/ApplicationOptions/CredentialSource.cs +++ b/src/Microsoft.Identity.Abstractions/ApplicationOptions/CredentialSource.cs @@ -191,5 +191,22 @@ public enum CredentialSource /// This is currently a Microsoft-Internal concept which has no meaning outside of Microsoft. /// ManagedCertificate = 12, + + /// + /// Use this value when you provide a certificate from the certificate store, described by its subject name. + /// When setting the property to this value, you'll also provide the + /// and properties. + /// + /// + /// + /// CurrentUser/My) and specified by its subject name, used as a client credential in a confidential client application: + /// :::code language="json" source="~/../abstractions-samples/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionTest.cs" id="subjectname_json"::: + /// + /// The code below describes programmatically in C#, a computer certificate in the personal certificates folder (LocalMachine/My) accessed by its subject name. + /// :::code language="csharp" source="~/../abstractions-samples/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionTest.cs" id="subjectname_csharp"::: + /// ]]> + /// + StoreWithSubjectName = 13, } } diff --git a/src/Microsoft.Identity.Abstractions/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Abstractions/PublicAPI/net10.0/PublicAPI.Unshipped.txt index 03f0cea..10190da 100644 --- a/src/Microsoft.Identity.Abstractions/PublicAPI/net10.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Abstractions/PublicAPI/net10.0/PublicAPI.Unshipped.txt @@ -1,4 +1,7 @@ #nullable enable +Microsoft.Identity.Abstractions.CredentialDescription.CertificateSubjectName.get -> string? +Microsoft.Identity.Abstractions.CredentialDescription.CertificateSubjectName.set -> void +Microsoft.Identity.Abstractions.CredentialSource.StoreWithSubjectName = 13 -> Microsoft.Identity.Abstractions.CredentialSource *REMOVED*Microsoft.Identity.Abstractions.CredentialDescription.Certificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2? *REMOVED*Microsoft.Identity.Abstractions.CredentialDescription.Certificate.set -> void *REMOVED*virtual Microsoft.Identity.Abstractions.CredentialDescription.CachedValue.get -> object? diff --git a/src/Microsoft.Identity.Abstractions/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Abstractions/PublicAPI/net462/PublicAPI.Unshipped.txt index e46d2cb..5a261d2 100644 --- a/src/Microsoft.Identity.Abstractions/PublicAPI/net462/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Abstractions/PublicAPI/net462/PublicAPI.Unshipped.txt @@ -1,3 +1,6 @@ #nullable enable +Microsoft.Identity.Abstractions.CredentialDescription.CertificateSubjectName.get -> string? +Microsoft.Identity.Abstractions.CredentialDescription.CertificateSubjectName.set -> void +Microsoft.Identity.Abstractions.CredentialSource.StoreWithSubjectName = 13 -> Microsoft.Identity.Abstractions.CredentialSource Microsoft.Identity.Abstractions.IBoundAuthorizationHeaderProvider Microsoft.Identity.Abstractions.IBoundAuthorizationHeaderProvider.CreateBoundAuthorizationHeaderAsync(Microsoft.Identity.Abstractions.DownstreamApiOptions! downstreamApiOptions, System.Security.Claims.ClaimsPrincipal? claimsPrincipal = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task>! diff --git a/src/Microsoft.Identity.Abstractions/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Abstractions/PublicAPI/net8.0/PublicAPI.Unshipped.txt index e46d2cb..5a261d2 100644 --- a/src/Microsoft.Identity.Abstractions/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Abstractions/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -1,3 +1,6 @@ #nullable enable +Microsoft.Identity.Abstractions.CredentialDescription.CertificateSubjectName.get -> string? +Microsoft.Identity.Abstractions.CredentialDescription.CertificateSubjectName.set -> void +Microsoft.Identity.Abstractions.CredentialSource.StoreWithSubjectName = 13 -> Microsoft.Identity.Abstractions.CredentialSource Microsoft.Identity.Abstractions.IBoundAuthorizationHeaderProvider Microsoft.Identity.Abstractions.IBoundAuthorizationHeaderProvider.CreateBoundAuthorizationHeaderAsync(Microsoft.Identity.Abstractions.DownstreamApiOptions! downstreamApiOptions, System.Security.Claims.ClaimsPrincipal? claimsPrincipal = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task>! diff --git a/src/Microsoft.Identity.Abstractions/PublicAPI/net9.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Abstractions/PublicAPI/net9.0/PublicAPI.Unshipped.txt index e46d2cb..5a261d2 100644 --- a/src/Microsoft.Identity.Abstractions/PublicAPI/net9.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Abstractions/PublicAPI/net9.0/PublicAPI.Unshipped.txt @@ -1,3 +1,6 @@ #nullable enable +Microsoft.Identity.Abstractions.CredentialDescription.CertificateSubjectName.get -> string? +Microsoft.Identity.Abstractions.CredentialDescription.CertificateSubjectName.set -> void +Microsoft.Identity.Abstractions.CredentialSource.StoreWithSubjectName = 13 -> Microsoft.Identity.Abstractions.CredentialSource Microsoft.Identity.Abstractions.IBoundAuthorizationHeaderProvider Microsoft.Identity.Abstractions.IBoundAuthorizationHeaderProvider.CreateBoundAuthorizationHeaderAsync(Microsoft.Identity.Abstractions.DownstreamApiOptions! downstreamApiOptions, System.Security.Claims.ClaimsPrincipal? claimsPrincipal = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task>! diff --git a/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index e46d2cb..5a261d2 100644 --- a/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,3 +1,6 @@ #nullable enable +Microsoft.Identity.Abstractions.CredentialDescription.CertificateSubjectName.get -> string? +Microsoft.Identity.Abstractions.CredentialDescription.CertificateSubjectName.set -> void +Microsoft.Identity.Abstractions.CredentialSource.StoreWithSubjectName = 13 -> Microsoft.Identity.Abstractions.CredentialSource Microsoft.Identity.Abstractions.IBoundAuthorizationHeaderProvider Microsoft.Identity.Abstractions.IBoundAuthorizationHeaderProvider.CreateBoundAuthorizationHeaderAsync(Microsoft.Identity.Abstractions.DownstreamApiOptions! downstreamApiOptions, System.Security.Claims.ClaimsPrincipal? claimsPrincipal = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task>! diff --git a/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt index e46d2cb..fc94c15 100644 --- a/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt @@ -1,3 +1,7 @@ #nullable enable +Microsoft.Identity.Abstractions.CredentialDescription.CertificateSubjectName.get -> string? +Microsoft.Identity.Abstractions.CredentialDescription.CertificateSubjectName.set -> void +Microsoft.Identity.Abstractions.CredentialSource.StoreWithSubjectName = 13 -> Microsoft.Identity.Abstractions.CredentialSource Microsoft.Identity.Abstractions.IBoundAuthorizationHeaderProvider Microsoft.Identity.Abstractions.IBoundAuthorizationHeaderProvider.CreateBoundAuthorizationHeaderAsync(Microsoft.Identity.Abstractions.DownstreamApiOptions! downstreamApiOptions, System.Security.Claims.ClaimsPrincipal? claimsPrincipal = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task>! + diff --git a/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionIdTest.cs b/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionIdTest.cs index d77f8c1..373cbf7 100644 --- a/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionIdTest.cs +++ b/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionIdTest.cs @@ -170,6 +170,17 @@ public void DisplayCredentialDescriptionIdsForAllSourceTypes() { "MyCustomProviderData_Key", "MyCustomProviderData_Data" } } } + }, + + // StoreWithSubjectName + { + CredentialSource.StoreWithSubjectName, + new CredentialDescription + { + SourceType = CredentialSource.StoreWithSubjectName, + CertificateStorePath = "CurrentUser/My", + CertificateSubjectName = "CN=WebAppCallingWebApiCert" + } } }; #pragma warning restore SYSLIB0026 // Type or member is obsolete @@ -471,5 +482,28 @@ public void CachedId_InvalidatedWhen_CachedValue_Changes() Assert.NotEqual(initialId, updatedId); Assert.Contains("Value2", updatedId, StringComparison.Ordinal); } + + [Fact] + public void CachedId_InvalidatedWhen_CertificateSubjectName_Changes() + { + // Arrange + var credentialDescription = new CredentialDescription + { + SourceType = CredentialSource.StoreWithSubjectName, + CertificateStorePath = "CurrentUser/My", + CertificateSubjectName = "CN=Cert1" + }; + + var initialId = credentialDescription.Id; + Assert.Contains("CN=Cert1", initialId, StringComparison.Ordinal); + + // Act + credentialDescription.CertificateSubjectName = "CN=Cert2"; + var updatedId = credentialDescription.Id; + + // Assert + Assert.NotEqual(initialId, updatedId); + Assert.Contains("CN=Cert2", updatedId, StringComparison.Ordinal); + } } } diff --git a/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionJsonConverterTest.cs b/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionJsonConverterTest.cs index 0231aa0..e964e21 100644 --- a/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionJsonConverterTest.cs +++ b/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionJsonConverterTest.cs @@ -76,6 +76,29 @@ public void SerializeDeserialize_CertificateFromStoreByThumbprint() Assert.Equal(CredentialType.Certificate, deserialized.CredentialType); } + [Fact] + public void SerializeDeserialize_CertificateFromStoreBySubjectName() + { + // Arrange + var original = new CredentialDescription + { + SourceType = CredentialSource.StoreWithSubjectName, + CertificateStorePath = "LocalMachine/My", + CertificateSubjectName = "CN=WebAppCallingWebApiCert" + }; + + // Act + string json = JsonSerializer.Serialize(original, _options); + var deserialized = JsonSerializer.Deserialize(json, _options); + + // Assert + Assert.NotNull(deserialized); + Assert.Equal(original.SourceType, deserialized.SourceType); + Assert.Equal(original.CertificateStorePath, deserialized.CertificateStorePath); + Assert.Equal(original.CertificateSubjectName, deserialized.CertificateSubjectName); + Assert.Equal(CredentialType.Certificate, deserialized.CredentialType); + } + [Fact] public void SerializeDeserialize_CertificateFromKeyVault() { diff --git a/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionTest.cs b/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionTest.cs index ef70a6d..0616cdd 100644 --- a/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionTest.cs +++ b/test/Microsoft.Identity.Abstractions.Tests/CredentialDescriptionTest.cs @@ -129,6 +129,36 @@ public void CertificateFromStoreByDistinguishedName() Assert.Equal(CredentialType.Certificate, credentialDescription.CredentialType); } + [Fact] + public void CertificateFromStoreBySubjectName() + { + // Certificate from credential store by subject name + // ------------------------------------------------- + /* + // + { + "ClientCredentials": [ + { + "SourceType": "StoreWithSubjectName", + "CertificateStorePath": "CurrentUser/My", + "CertificateSubjectName": "CN=WebAppCallingWebApiCert" + }] + } + // + */ + + // + CredentialDescription credentialDescription = new CredentialDescription + { + SourceType = CredentialSource.StoreWithSubjectName, + CertificateStorePath = "LocalMachine/My", + CertificateSubjectName = "CN=WebAppCallingWebApiCert" + }; + // + + Assert.Equal(CredentialType.Certificate, credentialDescription.CredentialType); + } + [Fact] public void CertificateFromKeyVault() {