Quantcast
Channel: Distributed Services: Notes from the field
Viewing all 164 articles
Browse latest View live

CSP Blobs between C# and C++ – Interoperation with the Microsoft Cryptographic API (CAPI)

$
0
0

If you have a requirement as follows:

 

  1. Interoperate between C# & C++ using cryptographic blobs.
  2. Generate the private and public keys in C#. See code below:

 

public void GenerateKeys(out byte[] privateKey, out byte[] publicKey)

{

    using (var rsa = new RSACryptoServiceProvider(2048))

    {

        rsa.PersistKeyInCsp = false;

        privateKey = rsa.ExportCspBlob(true);

        publicKey = rsa.ExportCspBlob(false);

    }

}

 

  1. Encrypt a file in C#.
  2. Decrypt it in C++.

 

You might get a failure with error code of 0x57 (Error code: (Win32) 0x57 (87) – The parameter is incorrect). This failure happens when you decrypt using the CryptDecrypt() API as shown in the code below.

 

if (!CryptDecrypt(hKey, NULL, TRUE, 0, pbData, &dwDataLen))

{

// Error

_tprintf(_T(“CryptDecrypt error 0x%x\n”), GetLastError());

return 1;

}

 

Changing dwFlags (the 4th parameter to CryptDecrypt) to CRYPT_DECRYPT_RSA_NO_PADDING_CHECK (see code below) will make the API succeed but the decryption result is undesirable.

 

if (!CryptDecrypt(hKey, NULL, TRUE, CRYPT_DECRYPT_RSA_NO_PADDING_CHECK, pbData, &dwDataLen))

{

// Error

_tprintf(_T(“CryptDecrypt error 0x%x\n”), GetLastError());

return 1;

}

 

The entire code succeeds if at the 3rd step you encrypt the file in C++.

 

Please note: Unlike the RSA implementation in unmanaged CAPI, the RSACryptoServiceProvider class reverses the order of an encrypted array of bytes after encryption and before decryption. By default, data encrypted by the RSACryptoServiceProvider class cannot be decrypted by the CAPI CryptDecrypt function and data encrypted by the CAPI CryptEncrypt method cannot be decrypted by the RSACryptoServiceProvider class.

To interoperate with CAPI, you must manually reverse the order of the encrypted bytes before the encrypted data interoperates with another API.

Example:

 

using (var rsa = new RSACryptoServiceProvider())

{

rsa.ImportCspBlob(blob);

// Input string.

const string input = “This is a test.”;

byte[] array = Encoding.ASCII.GetBytes(input);

byte[] encryptedData = rsa.Encrypt(array, false);

 

Array.Reverse(encryptedData, 0, encryptedData.Length);

using (var fs = new FileStream(encryptedFileName, FileMode.Create))

fs.Write(encryptedData, 0, encryptedData.Length);

}

 

Now you can use CryptDecrypt function to decrypt the file using the private key.

 

For reference see: https://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider(v=vs.110).aspx

 


Additional details on AF CU8 release

$
0
0

The latest version of AF 1.1 CU (cumulative update) 8 was released on 12/7/2016, and available on https://www.microsoft.com/en-us/download/details.aspx?id=54440. The detailed hotfix information is listed on https://support.microsoft.com/en-us/kb/3199763.

 

It has fix for the following three types of issues:

1.      Fixed a memory leak that occurs in specific scenarios. When a dependent service of AppFabric is unresponsive, this results in undisposed objects that accumulate over time. This issue is more noticeable in scenarios where the dependent services have regularly scheduled downtimes that aren’t synchronized with the AppFabric server’s downtime. 

2.      Fixed an intermittent crash issue. The scenario requires that the AppFabric server has several dependent services and many persisted workflows. The crash is rare but typically occurs during startup.

 3.      Fixed a crash in the AppFabric Event Collection Service when ETW events that are associated with Workflow Versioning are present. If the Workflow instances that are monitored in the AppFabric Dashboard use Workflow Versioning, ETW events that the Event Collection Service doesn’t recognize may be generated. This causes the Event Collection Service to crash.

 

For workflow versioning scenario (i.e. any new workflow feature .NET 4.5 onwards), AppFabric dashboard was not reporting all the events. It has been addressed with the current CU release.

 

The above hotfix contains fix from the AppFabric point of view (i.e. itself ready to handle the new events). However, the AF associated database (SQL) needs to be updated with new set of SQL scripts, which comes in the folder C:\Program Files\AppFabric 1.1 for Windows Server\Schema\ (in general). Schema folder is updated for only one file – Create_Monitoring_Schema.sql. This has the trigger definition script updated for ASWfEventsTable_AfterInsertTrigger, that allows tracking additional events.

 

Note: You would have to run the SQL script Create_Monitoring_Schema.sql explicitly once CU 8 (kb/3199763) is installed (for additional events tracked in AF dashboard).

 

 

I hope this helps!

WCF: Message Security limitation with TLS 1.2 protocol

$
0
0
Issue:
WCF Message Security breaks when using or forced to use TLS 1.1 or TLS 1.2 protocol.

Re-pro code:
https://1drv.ms/f/s!ArgnWb8iHXB6gqcg43hmT5jjbKJ-IA

We can disable SSL 3.0 and TLS 1.0 inside server key and we get below failure stack.

Failure Stack:
    29 clr!IL_Throw+0x184
    2a System_IdentityModel_ni!System.IdentityModel.SspiWrapper.AcquireCredentialsHandle(System.String, System.IdentityModel.CredentialUse, System.IdentityModel.SecureCredential)+0xd71ca
    2b System_ServiceModel_ni!System.ServiceModel.Security.TlsSspiNegotiation.AcquireDummyCredentials()+0x73
    2c System_ServiceModel_ni!System.ServiceModel.Security.TlsSspiNegotiation..ctor(System.String, Boolean, System.IdentityModel.SchProtocols, System.Security.Cryptography.X509Certificates.X509Certificate2, System.Security.Cryptography.X509Certificates.X509Certificate2, Boolean)+0xfc
    2d System_ServiceModel_ni!System.ServiceModel.Security.TlsnegoTokenProvider.CreateTlsSspiState(System.IdentityModel.Tokens.X509SecurityToken)+0x75
    2e System_ServiceModel_ni!System.ServiceModel.Security.TlsnegoTokenProvider.CreateNegotiationState(System.ServiceModel.EndpointAddress, System.Uri, System.TimeSpan)+0x41
    2f System_ServiceModel_ni!System.ServiceModel.Security.IssuanceTokenProviderBase`1[[System.__Canon, mscorlib]].DoNegotiation(System.TimeSpan)+0xd6
    30 System_ServiceModel_ni!System.ServiceModel.Security.SspiNegotiationTokenProvider.OnOpen(System.TimeSpan)+0x68
    31 System_ServiceModel_ni!System.ServiceModel.Security.TlsnegoTokenProvider.OnOpen(System.TimeSpan)+0xcb

Reason:

WCF does have these hard coded values..
TlsSspiNegotiation tlsNegotiation = new TlsSspiNegotiation(String.Empty, SchProtocols.Ssl3Client | SchProtocols.TlsClient, clientCertificate);
     internal enum SchProtocols
    {
        Zero = 0,
        Ssl2Client = 0x00000008,
        Ssl2Server = 0x00000004,
        Ssl2 = (Ssl2Client | Ssl2Server),
        Ssl3Client = 0x00000020,
        Ssl3Server = 0x00000010,
        Ssl3 = (Ssl3Client | Ssl3Server),
        TlsClient = 0x00000080,
        TlsServer = 0x00000040,
        Tls = (TlsClient | TlsServer),
        Ssl3Tls = (Ssl3 | Tls),
    };

Problem documentation and plan for fix:
Problem will be addressed and fixed in new framework release 4.7
https://github.com/Microsoft/dotnet-apiport/pull/395
https://github.com/Microsoft/dotnet/blob/master/releases/net47/dotnet47-changes.md#wcf

Workaround for existing framework 4.6.2
1. Add the configuration file setting:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <startup>
 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
 </startup>
 <runtime>
 <AppContextSwitchOverrides value="Switch.System.ServiceModel.DisableUsingServicePointManagerSecurityProtocols=false" />
 </runtime>
</configuration>

2. Get the private DLL from following location:
https://1drv.ms/f/s!ArgnWb8iHXB6gqRCG5I4nCw1vWbz8g

3. Path to copy them:
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.IdentityModel\v4.0_4.0.0.0__b77a5c561934e089\
And
C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.ServiceModel\v4.0_4.0.0.0__b77a5c561934e089\


More details:
Private build is added with support for TLS 1.1 and TLS 1.2 protocol.
untitled
I have tested the above private DLL for self host WCF service, but got new error when testing against IIS hosted WCF service.

Error for IIS hosted WCF Service:
untitled4
Workaround:
We can try to set the "EstablishSecurityContext" and "NegotiateServiceCredentials" to false at Message Security level and it should help us use WCF message security over TLS 1.1 / 1.2 protocol.
<message clientCredentialType="Certificate" establishSecurityContext="false"  negotiateServiceCredential="false" />

negotiateServiceCredential:
A Boolean value that specifies whether the service credential is provisioned at the client out of band, or is obtained from the service to the client through a process of negotiation. Such a negotiation is a precursor to the usual message exchange.
If the clientCredentialType attribute equals to None, Username, or Certificate, setting this attribute to false implies that the service certificate is available at the client out of band and that the client needs to specify the service certificate (using the <serviceCertificate>) in the <serviceCredentials> service behavior.
This mode is interoperable with SOAP stacks which implement WS-Trust and WS-SecureConversation.

If the ClientCredentialType attribute is set to Windows, setting this attribute to false specifies Kerberos based authentication.
This means that the client and service must be part of the same Kerberos domain. This mode is interoperable with SOAP stacks which implement the Kerberos token profile (as defined at OASIS WSS TC) as well as WS-Trust and WS-SecureConversation.

***When this attribute is true, it causes a .NET SOAP negotiation that tunnels SPNego exchange over SOAP messages. The default is true.


I hope this information helps to use WCF Message security correctly with TLS protocol.

Thanks
Saurabh Somani

WCF: Federating WCF with WIF

$
0
0

Ask:

Federate WCF service via WIF

 

Traditional approach:

For normal web app or MVC app, we follow the concept of FedAuth cookie.

Client -> Federated Application, gets redirected to STS

Client -> STS, get claims

Client -> Federated Application validates claims and issue a Fed Auth Cookie.

Client -> This time call made with Fed Auth cookie and Federated application serves the request.

 

Approach for WCF Service:

WCF service by default uses the ws2007FederationHttpBinding.

This is a special binding designed to federate the WCF service. However, it does not work on above traditional approach. Rather this works on WCF Message Security principle.

Client -> Federated Application, get redirected to STS

Client -> Does Security Negotiation to get the SCT (Security Context Token) via RST/Issue Action.

Client (using SCT Token and Claims received via STS) -> Federated Application, here claims can be checked.

untitled4

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

In above screen we can see that there is only one RST/Issue or SCT call made between two calls to “GetData()” method. Because with WCF federation, we work on concept of using the WCF channel or SCT token negotiated with STS. There is no concept of FedAuth cookie, because WCF Federation is all about using Message Security protocol.

 

https://msdn.microsoft.com/en-us/library/bb675187(v=vs.110).aspx

WsFederation2007HttpBindindm only support Message Level security.

<ws2007FederationBinding>

<binding >

<security mode=”None/Message/TransportWithMessageCredential”>

<message negotiateServiceCredential=”Boolean”

algorithmSuite=”Basic128/Basic192/Basic256/Basic128Rsa15/ Basic256Rsa15/TripleDes/TripleDesRsa15/Basic128Sha256/Basic192Sha256/TripleDesSha256/Basic128Sha256Rsa15/Basic192Sha256Rsa15/Basic256Sha256Rsa15/TripleDesSha256Rsa15″

defaultProtectionLevel=”none/sign/EncryptAndSign”

issuedTokenType=”string”

issuedKeyType=”SymmetricKey/PublicKey”

</message>

</security>

</binding>

</ws2007FederationBinding>

From below screen shot, we can see client doing message security negotiation with the ADFS STS and not the actual Federated WCF application:

untitled

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

But, also check that the Body contains the actual address of the Federated WCF application or in other words our relying party address.

Once negotiation completes client received the SAML Assertion token from the ADFS or our custom STS.

purush

 

 

Once received, client can now present the Claims along with the SCT token negotiated with the ADFS STS to the federated WCF application.

untitled5

 

 

Unfortunately, WCF traces truncates the Claims in WCF traces, so we cannot see it. However, we can clearly see the SAML Assertion ID.

Simple WCF-WIF Federation sample can be created by following the below blog: https://blogs.msdn.microsoft.com/napegadie_kones_msft_blog/2015/02/09/claims-aware-wcf-using-wif-in-net-4-5/

 

Client application configuration: Caller to Federated WCF service

<system.serviceModel>

<diagnostics>

<messageLogging logEntireMessage=”true” logKnownPii=”true” logMalformedMessages=”true”

logMessagesAtServiceLevel=”true” logMessagesAtTransportLevel=”true” />

</diagnostics>

<bindings>

<wsHttpBinding>

<binding name=”https://adfs.contoso.com/adfs/services/trust/13/windowstransport”>

<security mode=”Transport” />

</binding>

</wsHttpBinding>

<ws2007FederationHttpBinding>

<binding name=”WS2007FederationHttpBinding_IService1″>

<security mode=”TransportWithMessageCredential”>

<message establishSecurityContext=”false” negotiateServiceCredential=”false”>

<issuer address=”https://adfs.contoso.com/adfs/services/trust/13/windowstransport”

binding=”wsHttpBinding” bindingConfiguration=”https://adfs.contoso.com/adfs/services/trust/13/windowstransport”>

<identity>

<servicePrincipalName value=”host/adfs.contoso.com” />

</identity>

</issuer>

<issuerMetadata address=”https://adfs.contoso.com/adfs/services/trust/mex” />

<tokenRequestParameters>

<trust:SecondaryParameters xmlns:trust=”http://docs.oasis-open.org/ws-sx/ws-trust/200512″>

<trust:KeyType xmlns:trust=”http://docs.oasis-open.org/ws-sx/ws-trust/200512″>http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey</trust:KeyType>

<trust:KeySize xmlns:trust=”http://docs.oasis-open.org/ws-sx/ws-trust/200512″>256</trust:KeySize>

<trust:KeyWrapAlgorithm xmlns:trust=”http://docs.oasis-open.org/ws-sx/ws-trust/200512″>http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p</trust:KeyWrapAlgorithm>

<trust:EncryptWith xmlns:trust=”http://docs.oasis-open.org/ws-sx/ws-trust/200512″>http://www.w3.org/2001/04/xmlenc#aes256-cbc</trust:EncryptWith>

<trust:SignWith xmlns:trust=”http://docs.oasis-open.org/ws-sx/ws-trust/200512″>http://www.w3.org/2000/09/xmldsig#hmac-sha1</trust:SignWith>

<trust:CanonicalizationAlgorithm xmlns:trust=”http://docs.oasis-open.org/ws-sx/ws-trust/200512″>http://www.w3.org/2001/10/xml-exc-c14n#</trust:CanonicalizationAlgorithm>

<trust:EncryptionAlgorithm xmlns:trust=”http://docs.oasis-open.org/ws-sx/ws-trust/200512″>http://www.w3.org/2001/04/xmlenc#aes256-cbc</trust:EncryptionAlgorithm>

</trust:SecondaryParameters>

</tokenRequestParameters>

</message>

</security>

</binding>

</ws2007FederationHttpBinding>

</bindings>

<client>

<endpoint address=”https://aadconnect.contoso.com/WcfService1/Service1.svc”

binding=”ws2007FederationHttpBinding” bindingConfiguration=”WS2007FederationHttpBinding_IService1″

contract=”ServiceReference1.IService1″ name=”WS2007FederationHttpBinding_IService1″ />

</client>

</system.serviceModel>

 

WCF Federated Service configuration:

<system.identityModel>

<identityConfiguration>

<audienceUris>

<add value=”https://aadconnect.contoso.com/WcfService1/Service1.svc” />

</audienceUris>

<issuerNameRegistry type=”System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry”>

<authority name=”http://adfs.contoso.com/adfs/services/trust”>

<keys>

<add thumbprint=”FBB1052A9E412FFBEB0032D190DECE12E32C4689″ />

</keys>

<validIssuers>

<add name=”http://adfs.contoso.com/adfs/services/trust” />

</validIssuers>

</authority>

</issuerNameRegistry>

<!–certificationValidationMode set to “None” by the the Identity and Access Tool for Visual Studio. For development purposes.–>

<certificateValidation certificateValidationMode=”None” />

</identityConfiguration>

</system.identityModel>

<configSections>

<section name=”system.identityModel” type=”System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089″ />

</configSections>

<appSettings>

<add key=”aspnet:UseTaskFriendlySynchronizationContext” value=”true” />

<add key=”ida:FederationMetadataLocation” value=”https://adfs.contoso.com/federationmetadata/2007-06/federationmetadata.xml” />

<add key=”ida:ProviderSelection” value=”productionSTS” />

</appSettings>

<location path=”FederationMetadata”>

<system.web>

<authorization>

<allow users=”*” />

</authorization>

</system.web>

</location>

<system.serviceModel>

<behaviors>

<serviceBehaviors>

<behavior>

<serviceMetadata httpGetEnabled=”true” httpsGetEnabled=”true” />

<serviceDebug includeExceptionDetailInFaults=”false” />

<serviceCredentials useIdentityConfiguration=”true”>

<serviceCertificate findValue=”CN=localhost” storeLocation=”LocalMachine” storeName=”My” x509FindType=”FindBySubjectDistinguishedName” />

</serviceCredentials>

</behavior>

</serviceBehaviors>

</behaviors>

<protocolMapping>

<add scheme=”https” binding=”ws2007FederationHttpBinding” />

</protocolMapping>

<serviceHostingEnvironment aspNetCompatibilityEnabled=”true” multipleSiteBindingsEnabled=”true” />

<bindings>

<ws2007FederationHttpBinding>

<binding name=””>

<security mode=”TransportWithMessageCredential”>

<message establishSecurityContext=”false” negotiateServiceCredential=”false”>

<issuerMetadata address=”https://adfs.contoso.com/adfs/services/trust/mex” />

</message>

</security>

</binding>

</ws2007FederationHttpBinding>

</bindings>

</system.serviceModel>

Sample Application:

https://1drv.ms/f/s!ArgnWb8iHXB6gqYXvg53egkGHFxdvw

WIF: WIF10201: No valid key mapping found for securityToken:

$
0
0

Issue:
WIF10201: No valid key mapping found for securityToken:
This exception is observed on a federated application(web app / mvc / asmx / wcf) using WIF pipeline to authenticate the user.

Stack:
[SecurityTokenValidationException: WIF10201: No valid key mapping found for securityToken: ‘System.IdentityModel.Tokens.X509SecurityToken’ and issuer: ‘LocalSTS’.]
System.IdentityModel.Tokens.SamlSecurityTokenHandler.ValidateToken(SecurityToken token) +987
System.IdentityModel.Tokens.SecurityTokenHandlerCollection.ValidateToken(SecurityToken token) +73
System.IdentityModel.Services.TokenReceiver.AuthenticateToken(SecurityToken token, Boolean ensureBearerToken, String endpointUri) +110
System.IdentityModel.Services.WSFederationAuthenticationModule.SignInWithResponseMessage(HttpRequestBase request) +527
System.IdentityModel.Services.WSFederationAuthenticationModule.OnAuthenticateRequest(Object sender, EventArgs args) +381
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +141
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +69

Root cause:
This error is triggered because of the mandatory code validation in WIF module to check the incoming request Signing token.

// System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry
public override string GetIssuerName(SecurityToken securityToken)
{
if (securityToken == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(“securityToken”);
}
X509SecurityToken x509SecurityToken = securityToken as X509SecurityToken;
if (x509SecurityToken != null)
{
  string thumbprint = x509SecurityToken.Certificate.Thumbprint;  <—————-
if (this._configuredTrustedIssuers.ContainsKey(thumbprint))
{
string text = this._configuredTrustedIssuers[thumbprint];
text = (string.IsNullOrEmpty(text) ? x509SecurityToken.Certificate.Subject : text);
if (TD.GetIssuerNameSuccessIsEnabled())
{
TD.GetIssuerNameSuccess(EventTraceActivity.GetFromThreadOrCreate(false), text, securityToken.Id);
}
return text;
}
}
if (TD.GetIssuerNameFailureIsEnabled())
{
TD.GetIssuerNameFailure(EventTraceActivity.GetFromThreadOrCreate(false), securityToken.Id);
}
return null;
}

Summary:

So eventually we try to read the current token associated with the request
string thumbprint = x509SecurityToken.Certificate.Thumbprint

And then compare it with the config file entry for our project.
string text = this._configuredTrustedIssuers[thumbprint];

Two checks done:
===========
1. Validate if the existing request security token's thumbprint is already present inside the in memory dictionary object
this._configuredTrustedIssuers.

2. If it is not there, we return Null.
3. If it is there, we compare the  subject name of incoming token with in memory value and pass from there..

All these check are to make sure that we receive the request signed from same issuer which was initially trusted on the main app.


Solution:
To solve this error we need to make sure we have added proper key inside the Federated app configuration file to respective issuer.

<system.identityModel>

<identityConfiguration>

<audienceUris>

<add value=http://localhost:1442/ />

</audienceUris>

<issuerNameRegistry type=System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry>

<authority name=LocalSTS>

<keys>

<add thumbprint=9B74CB2F320F7AAFC156E1252270B1DC01EF40D1 />

</keys>

<validIssuers>

<add name=LocalSTS />

</validIssuers>

</authority>

</issuerNameRegistry>

<certificateValidation certificateValidationMode=None />

</identityConfiguration>

</system.identityModel>

If the thumbprint used here is invalid and does not match with the incoming Token - Base 4 encoded value. It fails!


Solution:
To get it working, we would need to extract and use the correct thumbprint.

1. Check the Base 64 encoded value of incoming request via fiddler or Freb logs and find the correct Signing Cert at STS. Get the thumbprint.
2. Use below code to create Thumbprint out of base 64 encoded value.

string _publicKey = “Base64encodedString”;

var cert = new X509Certificate2(Convert.FromBase64String(_publicKey), “password”, X509KeyStorageFlags.PersistKeySet);

if (cert != null)

{

//use Cert

}

I hope this helps everyone stuck with same error.

Thanks
Saurabh Somani

ADAL: Secure Web API with ADFS 3.0 for Desktop Client

$
0
0

I came across one of the requirements, where my customer requested me to create a sample ASP.NET WEB API application, and later be consumed by a rich desktop client like WPF. It had one OAuth 2.0 protocol authorization rider before accessing the WEB API resource. And, the OAuth 2.0 access token must be retrieved from an On-Premise ADFS authorization server.

 

OAuth 2.0 authorization protocol is supported from ADFS 2012 and beyond.

 

 

Create Web API application

  1. Launch Visual Studio 2015 as an administrator
  2. File -> New -> Project
  3. Pick Visual C# -> Web -> ASP.NET Web Application template
  4. Name your application as MyWebAPIsample
  5. Hit OK
  6. Select Web API template and click on Change Authentication

adal1

  1. Select Work And School Accounts, pick On-Premises value from the drop down list
  2. On-Premises Authority will have value of https://adfs.contoso.com/federationmetadata/2007-06/federationmetadata.xml
  3. App ID URI will have value of https://win7.contoso.com/MyWebAPIsample/

adal2

  1. Say OK
  2. OK again
  3. Now, ASP.NET Web API application project will be ready
  4. This utilized the OWIN modules, which helped generating the authenticating modules internally – developer won’t have to write any explicit for authentication on this
  5. Web.config will look like:
   <appSettings>
       <add key="webpages:Version" value="3.0.0.0" />
       <add key="webpages:Enabled" value="false" />
       <add key="ClientValidationEnabled" value="true" />
       <add key="UnobtrusiveJavaScriptEnabled" value="true" />
       <add key="ida:AdfsMetadataEndpoint" value="https://adfs.contoso.com/federationmetadata/2007-06/federationmetadata.xml" />
       <add key="ida:Audience" value="https://win7.contoso.com/MyWebAPIsample/" />
   </appSettings>
  1. Startup.cs will have:
using Owin;
 
namespace MyWebAPIsample
{
    public partial class Startup
    {
         public void Configuration(IAppBuilder app)
         {
              ConfigureAuth(app);
         }
    }
}
  1. App_Start\Startup.Auth.cs will have:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IdentityModel.Tokens;
using System.Linq;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.ActiveDirectory;
using Owin;
 
namespace MyWebAPIsample
{
    public partial class Startup
    {
         // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
         public void ConfigureAuth(IAppBuilder app)
         {
              app.UseActiveDirectoryFederationServicesBearerAuthentication(
                 new ActiveDirectoryFederationServicesBearerAuthenticationOptions
                 {
                      MetadataEndpoint = ConfigurationManager.AppSettings["ida:AdfsMetadataEndpoint"],
                      TokenValidationParameters = new TokenValidationParameters() {
                           ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
                       }
                 });
          }
     }
}
  1. The controller ValuesController will have:
using System.Web.Http;
 
namespace MyWebAPIsample.Controllers
{
       [Authorize]
       public class ValuesController : ApiController
       {
           // GET api/values
           public IEnumerable<string> Get()
           {
                 return new string[] { "value1", "value2" };
           }
    ...
  1. Build the web API application

 

Host the WEB API application on IIS

  1. Right click the MyWebAPIsample project, go to Properties, and then to Web tab
  2. Set for Local IIS server, provide Project URL and click Create Virtual Directory
  3. Application would be deployed on IIS

adal3

  1. Otherwise, you are free to follow your own method of deploying the WEB API application on IIS
  2. Go to IIS manager (Run -> inetmgr) -> Application Pools
  3. Click on Add Application Pool

adal4

  1. Select your application under the sites
  2. Right click -> Manage Application -> Advanced Settings -> select the newly created application pool WebPool

adal5

  1. Web API application hosting is over

 

ADFS provisioning for Web API application

  1. Launch Windows PowerShell as an administrator
  2. Run the following command:

Add-ADFSRelyingPartyTrust -Name WIN7.MyWebAPIsample -Identifier https://win7.contoso.com/MyWebAPIsample/ -IssuanceAuthorizationRules '=> issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");' -IssuanceTransformRules 'c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"] => issue(claim = c);'

  1. Now, the Web API is provisioned as a known RP in ADFS
  2. Pick a GUID for ClientId [simple way: Solution explorer -> MyWebAPIsample project -> Properties -> AssemblyInfo.cs -> select Guid Value]
  3. Run the following command:

Add-ADFSClient -Name "WIN7.MyWebAPIsample.Client" -ClientId "09c9a8a2-6bf1-427d-89ba-45c2c02bb9fc" -RedirectUri "http://anarbitraryreturnuri/"

  1. Now, OAuth 2.0 client will be registered with ADFS

 

Consume the Web API on rich client

  1. Go to your solution explorer
  2. Add New Project -> Visual C# -> Windows -> WPF Application -> Name as  MyWebAPIClient
  3. Install NuGet package named Microsoft.IdentityModel.Clients.ActiveDirectory – recent version is 3.13.9.1126
  4. Add reference for System.Net.Http.dll
  5. Add a button control (add button click event) on MainWindow.xaml

<Grid>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="124,78,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
</Grid>

  1. Go to the code behind file – MainWindow.xaml.cs
  2. Add the button click event as
 
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Net.Http;
 
      private async void button_Click(object sender, RoutedEventArgs e)
      {
           string authority = "https://adfs.contoso.com/adfs";
           string resourceURI = "https://win7.contoso.com/MyWebAPIsample/";
           string clientID = "09c9a8a2-6bf1-427d-89ba-45c2c02bb9fc";
           string clientReturnURI = "http://anarbitraryreturnuri/";
 
           var authContext = new AuthenticationContext(authority, false);
           var authResult = await authContext.AcquireTokenAsync(resourceURI, clientID, new Uri(clientReturnURI), new PlatformParameters(PromptBehavior.Auto));
 
           string authHeader = authResult.CreateAuthorizationHeader();
 
           var client = new HttpClient();
           var request = new HttpRequestMessage(HttpMethod.Get, "https://win7.contoso.com/MyWebAPIsample/api/values");
           request.Headers.TryAddWithoutValidation("Authorization", authHeader);
           var response = await client.SendAsync(request);
           string responseString = await response.Content.ReadAsStringAsync();
           MessageBox.Show(responseString);
     }
  1. Build application

 

Debug and run the rich client

  1. Set as startup project for MyWebAPIClient
  2. Hit F5

    adal6

    1. Provide your credentials, hit F10

    adal7

     

    adal8

     

    adal9

    1. The testing is over.

     

    References

     

    Note:

    1. The same solution can be followed for ADFS 2016 as well. I was using ADFS 2012 in my case.
    2. Sample application is upload for your reference on OneDrive (please click to download it).

     

     

    Happy Programming!!!

    WIF: Memory leak issue with WIF 3.5 – Microsoft.IdentityModel.Tokens – SecurityTokenCacheKey

    $
    0
    0
    Issue:Recently we came across a case where memory leak issue is identified within WIF 3.5 DLL inside the "Microsoft.IdentityModel.Tokens" class.
    
    How does this happen:
    The source code has a coding BUG where "SecurityTokenCacheKey" are not getting cleared off properly.
    
    Impacted heap structures seen in memory dumps:
    00007ff97bbb0038   371165     17815920 System.Collections.Generic.LinkedListNode`1[[System.Object, mscorlib]]
    00007ff97bba6f48   371230     17819040 Microsoft.IdentityModel.Tokens.SecurityTokenCacheKey
    00007ff9d8ebf6c0   454411     49868362 System.String
    00007ff9d8ec5140   317533     66612435 System.Byte[]
    Root cause:
    These objects are rooted inside the WCF Session
    0:000> !gcroot 000000a3d91bd030
    Thread 1b7c:
        000000a82549e270 00007ff9d7a23574 System.Runtime.IOThreadTimer+TimerManager.OnWaitCallback(System.Object)
            rbx:
                ->  000000a65942cc90 System.Runtime.IOThreadTimer+TimerManager
                ->  000000a65942cd08 System.Runtime.IOThreadTimer+TimerGroup
                ->  000000a65942cd80 System.Runtime.IOThreadTimer+TimerQueue
                ->  000000a6596a3bd0 System.Object[]
                ->  000000a65942cc50 System.Runtime.IOThreadTimer
                ->  000000a65942cc10 System.Action`1[[System.Object, mscorlib]]
                ->  000000a659405b50 System.ServiceModel.Security.SecuritySessionServerSettings
                ->  000000a6593f5c60 System.ServiceModel.Security.SessionSymmetricMessageSecurityProtocolFactory  <------------------
                ->  000000a659405a78 System.ServiceModel.Security.SecurityStandardsManager
                ->  000000a6594038e8 Microsoft.IdentityModel.Tokens.SecurityTokenSerializerAdapter
                ->  000000a6593c5da0 Microsoft.IdentityModel.Tokens.SecurityTokenHandlerCollection
                ->  000000a6593c5e78 System.Collections.Generic.List`1[[Microsoft.IdentityModel.Tokens.SecurityTokenHandler, Microsoft.IdentityModel]]
                ->  000000a6593c63f8 System.Object[]
                ->  000000a6593bbdc8 Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler
                ->  000000a6593bbec8 Microsoft.IdentityModel.MruSecurityTokenCache
                ->  000000a6593bbf28 System.Collections.Generic.Dictionary`2[[System.Object, mscorlib],[Microsoft.IdentityModel.MruSecurityTokenCache+CacheEntry, Microsoft.IdentityModel]]
                ->  000000a801191038 System.Collections.Generic.Dictionary`2+Entry[[System.Object, mscorlib],[Microsoft.IdentityModel.MruSecurityTokenCache+CacheEntry, Microsoft.IdentityModel]][]
                ->  000000a5da531be8 System.Collections.Generic.LinkedListNode`1[[System.Object, mscorlib]]
                ->  000000a6593c5d70 System.Collections.Generic.LinkedList`1[[System.Object, mscorlib]]
                ->  000000a65b0f25b8 System.Collections.Generic.LinkedListNode`1[[System.Object, mscorlib]]
                ->  000000a4d91b2a98 System.Collections.Generic.LinkedListNode`1[[System.Object, mscorlib]]
                ->  000000a4d91cfbe8 System.Collections.Generic.LinkedListNode`1[[System.Object, mscorlib]]
                ->  000000a4d91cfd10 System.Collections.Generic.LinkedListNode`1[[System.Object, mscorlib]]
    
    
    0:000> !gcroot 000000a3d9eac378
    Thread 1b7c:
        000000a82549e270 00007ff9d7a23574 System.Runtime.IOThreadTimer+TimerManager.OnWaitCallback(System.Object)
            rbx:
                ->  000000a65942cc90 System.Runtime.IOThreadTimer+TimerManager
                ->  000000a65942cd08 System.Runtime.IOThreadTimer+TimerGroup
                ->  000000a65942cd80 System.Runtime.IOThreadTimer+TimerQueue
                ->  000000a6596a3bd0 System.Object[]
                ->  000000a65942cc50 System.Runtime.IOThreadTimer
                ->  000000a65942cc10 System.Action`1[[System.Object, mscorlib]]
                ->  000000a659405b50 System.ServiceModel.Security.SecuritySessionServerSettings
                ->  000000a6593f5c60 System.ServiceModel.Security.SessionSymmetricMessageSecurityProtocolFactory
                ->  000000a659405a78 System.ServiceModel.Security.SecurityStandardsManager
                ->  000000a6594038e8 Microsoft.IdentityModel.Tokens.SecurityTokenSerializerAdapter
                ->  000000a6593c5da0 Microsoft.IdentityModel.Tokens.SecurityTokenHandlerCollection
                ->  000000a6593c5e78 System.Collections.Generic.List`1[[Microsoft.IdentityModel.Tokens.SecurityTokenHandler, Microsoft.IdentityModel]]
                ->  000000a6593c63f8 System.Object[]
                ->  000000a6593bbdc8 Microsoft.IdentityModel.Tokens.SessionSecurityTokenHandler   <----------------
                ->  000000a6593bbec8 Microsoft.IdentityModel.MruSecurityTokenCache   <---------------------------------
                ->  000000a6593bbf28 System.Collections.Generic.Dictionary`2[[System.Object, mscorlib],[Microsoft.IdentityModel.MruSecurityTokenCache+CacheEntry, Microsoft.IdentityModel]]
                ->  000000a801191038 System.Collections.Generic.Dictionary`2+Entry[[System.Object, mscorlib],[Microsoft.IdentityModel.MruSecurityTokenCache+CacheEntry, Microsoft.IdentityModel]][]
                ->  000000a5da531be8 System.Collections.Generic.LinkedListNode`1[[System.Object, mscorlib]]
                ->  000000a6593c5d70 System.Collections.Generic.LinkedList`1[[System.Object, mscorlib]]
                ->  000000a65b0f25b8 System.Collections.Generic.LinkedListNode`1[[System.Object, mscorlib]]
                ->  000000a4d91b2a98 System.Collections.Generic.LinkedListNode`1[[System.Object, mscorlib]]
                ->  000000a4d91cfbe8 System.Collections.Generic.LinkedListNode`1[[System.Object, mscorlib]]
                ->  000000a4d91cfd10 System.Collections.Generic.LinkedListNode`1[[System.Object, mscorlib]]
    
    
    Relevant call stack:
    0:046> !clrstack
    OS Thread Id: 0x3770 (46)
            Child SP               IP Call Site
    000000a826d7cdc0 00007ff9d836ef84 System.Collections.Generic.LinkedList`1[[System.__Canon, mscorlib]].Find(System.__Canon)
    000000a826d7ce20 00007ff97bc84f10 Microsoft.IdentityModel.MruSecurityTokenCache.TryRemoveAllEntries(System.Object)
    000000a826d7cf70 00007ff9d69700e9 System.ServiceModel.Security.SecuritySessionServerSettings+ServerSecuritySessionChannel.CloseCore(System.TimeSpan)
    000000a826d7cff0 00007ff9d696df41 System.ServiceModel.Security.SecuritySessionServerSettings+ServerSecurityDuplexSessionChannel.CloseCore(System.TimeSpan)
    000000a826d7d030 00007ff9d696e28c System.ServiceModel.Security.SecuritySessionServerSettings+ServerSecurityDuplexSessionChannel.OnClose(System.TimeSpan)
    000000a826d7d0a0 00007ff9d5ce38d8 System.ServiceModel.Channels.CommunicationObject.Close(System.TimeSpan)
    000000a826d7d1e0 00007ff9d5cf75d6 System.ServiceModel.Channels.ServiceChannel.OnClose(System.TimeSpan)
    000000a826d7d240 00007ff9d5ce38d8 System.ServiceModel.Channels.CommunicationObject.Close(System.TimeSpan)  <----------
    
    
    So eventually all the TokenCacheKey object are actually referenced by:
    public class MruSecurityTokenCache : SecurityTokenCache
    {
    }
    
    
    Solution:
    After discussing with WIF PG, we confirmed that it was identified as internal BUG long time back and already fixed in latest framework release (4.6.2).
    With new release, WIF is tightly integrated with framework and issue does not happen.
    
    
    Hope this helps!
    
    Thanks
    Saurabh Somani

    WCF/WS/TLS: Get .Net Framework 4.0 application use TLS1.2 as default protocol

    $
    0
    0

    Issue:
    By default, .net application built on framework 4.0 will use SSL3.0 or TLS1.0 as default protocol.

    Ask:
    If we need to force it to use TLS1.2 protocol, review below workarounds.

    Workaround 1:
    Use below link just before Https call is attempted.
    ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;

    Workaround 2:
    Migrate the existing application to supported framework 4.6.2. After migrating client app will be default use the TLS1.2 protocol.
    However, I observed that it works well for console app but not for ASMX web service migration.

    Please review below steps to get web service use TLS 1.2 by default to make outbound calls.

    SSL HandShake ClientHello receives Encrypted Alert


    https://technet.microsoft.com/en-us/library/mt791311(v=office.16).aspx

    Set the registry key “SchUseStrongCrypto” to 1. Which will force .net application to avoid using SSL3.0 or TLS1.0 and it will always use TLS1.2

    I hope this helps!

    Thanks
    Saurabh Somani


    ASMX/WS/WCF Web Service: System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host

    $
    0
    0

    Issue:
    Intermittent Socket exception seen on client application trying to fetch data from MS web services.

    Troubleshooting:
    I recommend collecting application level traces to collect the stack trace information. In addition we can collect the System.Net traces or memory dumps on specific exceptions.

    Detailed stack from dump:
    0:000> !dumpstack
    OS Thread Id: 0x1708 (0)
    Current frame: KERNELBASE!RaiseException+0x58
    ChildEBP RetAddr Caller, Callee
    002dd7fc 7503c54f KERNELBASE!RaiseException+0x58, calling ntdll!RtlRaiseException
    002dd82c 72440769 (MethodDesc 7228a444 +0x401 System.Net.ConnectStream.CloseInternal(Boolean, Boolean)), calling (MethodDesc 722f5b60 +0 System.Net.ConnectStream.CallDone(System.Net.ConnectionReturnResult))
    002dd844 7402a769 clr!RaiseTheExceptionInternalOnly+0x27c, calling kernel32!RaiseExceptionStub
    002dd8e0 7402af54 clr!IL_Throw+0x138, calling clr!RaiseTheExceptionInternalOnly
    002dd944 7402aea1 clr!IL_Throw+0x3c, calling clr!LazyMachStateCaptureState
    002dd94c 7402c4a4 clr!IL_Rethrow+0x27, calling clr!LazyMachStateCaptureState
    002dd950 72887df0 (MethodDesc 722f5ca4 +0x140 System.Net.ConnectStream.IOError(System.Exception, Boolean)), calling (MethodDesc 722f5b60 +0 System.Net.ConnectStream.CallDone(System.Net.ConnectionReturnResult))
    002dd970 723f6845 (MethodDesc 722f5bb4 +0xc5 System.Net.ConnectStream.ReadWithoutValidation(Byte[], Int32, Int32, Boolean)), calling clr!IL_Rethrow
    002dd9a8 723f6726 (MethodDesc 7228a3dc +0x136 System.Net.ConnectStream.Read(Byte[], Int32, Int32)), calling clr!IL_Throw
    002ddaa0 73018a56 (MethodDesc 72d67e88 +0xe2 System.IO.StreamReader.ReadBuffer(Char[], Int32, Int32, Boolean ByRef))
    002ddac0 7301893b (MethodDesc 72d67e34 +0x7b System.IO.StreamReader.Read(Char[], Int32, Int32)), calling (MethodDesc 72d67e88 +0 System.IO.StreamReader.ReadBuffer(Char[], Int32, Int32, Boolean ByRef))
    002ddae8 7010c168 (MethodDesc 70069474 +0xe8 System.Xml.XmlTextReaderImpl.ReadData())

    The code, clearly indicates that we are indeed running in a catch block and thus error is thrown.
    https://referencesource.microsoft.com/#System/net/System/Net/_ConnectStream.cs,17c93c1ab8aca75d

    To mitigate the issue, following workaround is suggested (please review with your network admin before trying them):

    Command to disable SNP features :
    netsh interface tcp set global chimney=disabled
    netsh interface tcp set global rss=disabled
    netsh int tcp set global autotuning=disabled
    netsh int tcp set global congestion=none
    netsh int tcp set global netdma=Disabled

    For disabling the offloading:
    Open NCPA.cpl on command prompt and open NIC properties and disable the following features :
    ARP offload —–Disable
    Jumbo packet —–Disable
    ipv4 Checksum Offload —–Disable
    Jumbo packet —–Disable
    Large send Offload V2 (ipv4) —–Disable
    Large send Offload V2 (ipv6) —–Disable
    NS offload- —-Disable
    TCP Checksum Offload (ipv4) —–Disable
    TCP Checksum Offload (ipv6) —–Disable
    UDP Checksum Offload (ipv4) —–Disable
    UDP Checksum Offload (Ipv6) —–Disable

    References:
    http://blogs.technet.com/b/onthewire/archive/2014/01/21/tcp-offloading-chimney-amp-rss-what-is-it-and-should-i-disable-it.aspx
    https://support.microsoft.com/en-us/help/951037/information-about-the-tcp-chimney-offload,-receive-side-scaling,-and-network-direct-memory-access-features-in-windows-server-2008

    Hope this helps!

    Thanks
    Saurabh Somani

    WCF/WS: SSL Mutual Client Cert Authentication 403.16 or 403.7

    $
    0
    0

    Problem
    When attempting to use a certificate to authenticate to an IIS website or self hosted WCF service over SSL/TLS channel, we receive a 403.16 error code.

    Troubleshooting
    We can collect server side System.Net Traces or WCF Activity Traces

    System.Net Tracing collection Steps

    WCF Tracing

    Observation from System.Net Traces:
    You might observe the GetClientCertificate API was successful and Client Cert was received at server side.
    From WCF traces you might see a native error (0X109) being sent with below stack.

    http://msdn.microsoft.com/en-US/library/System.ServiceModel.Channels.HttpsClientCertificateInvalid.aspx
    Client certificate is invalid with native error code 0x109 (see http://go.microsoft.com/fwlink/?LinkId=187517 for details).
    Process.exe
    System.ServiceModel.Channels.HttpsChannelListener`1[System.ServiceModel.Channels.IReplyChannel]/11268815
    Keep-Alive
    text/html, application/xhtml+xml, */*
    gzip, deflate
    en-US
    X.X.X.X:YY
    Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
    https://Service/Method
    at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
    at System.Environment.get_StackTrace()
    at System.Diagnostics.TraceEventCache.get_Callstack()
    at System.Diagnostics.XmlWriterTraceListener.WriteFooter(TraceEventCache eventCache)
    at System.Diagnostics.XmlWriterTraceListener.TraceData(TraceEventCache eventCache, String source, TraceEventType eventType, Int32 id, Object data)
    at System.Diagnostics.TraceSource.TraceData(TraceEventType eventType, Int32 id, Object data)
    at System.ServiceModel.Diagnostics.LegacyDiagnosticTrace.TraceEvent(TraceEventType type, Int32 code, String msdnTraceCode, String description, TraceRecord trace, Exception exception, Object source)
    at System.ServiceModel.Diagnostics.TraceUtility.TraceEvent(TraceEventType severity, Int32 traceCode, String traceDescription, TraceRecord extendedData, Object source, Exception exception)
    at System.ServiceModel.Channels.HttpsChannelListener`1.ValidateAuthentication(HttpListenerContext listenerContext)
    at System.ServiceModel.Channels.HttpRequestContext.ListenerHttpContext.ValidateAuthentication()
    at System.ServiceModel.Channels.HttpRequestContext.ProcessAuthentication()
    at System.ServiceModel.Channels.HttpChannelListener`1.HttpContextReceivedAsyncResult`1.Authenticate()
    at System.ServiceModel.Channels.HttpChannelListener`1.HttpContextReceivedAsyncResult`1.ProcessHttpContextAsync()
    at System.ServiceModel.Channels.HttpChannelListener`1.BeginHttpContextReceived(HttpRequestContext context, Action acceptorCallback, AsyncCallback callback, Object state)
    at System.ServiceModel.Channels.SharedHttpTransportManager.EnqueueContext(IAsyncResult listenerContextResult)
    at System.ServiceModel.Channels.SharedHttpTransportManager.OnGetContextCore(IAsyncResult listenerContextResult)
    at System.ServiceModel.Channels.SharedHttpTransportManager.OnGetContext(IAsyncResult result)
    at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
    at System.Net.LazyAsyncResult.Complete(IntPtr userToken)
    at System.Net.LazyAsyncResult.ProtectedInvokeCallback(Object result, IntPtr userToken)
    at System.Net.ListenerAsyncResult.IOCompleted(ListenerAsyncResult asyncResult, UInt32 errorCode, UInt32 numBytes)
    at System.Net.ListenerAsyncResult.WaitCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
    at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)

    Cause
    The cause of this was due to having non-self-signed certs in the Trusted Root Certificate Authority store on the IIS server.
    https://support.microsoft.com/en-us/kb/2802568

    If we have a non-self-signed certificates in the Trusted Root certificate store. We can run this command to check:
    Get-Childitem cert:\LocalMachine\root -Recurse | Where-Object {$_.Issuer -ne $_.Subject} | Format-List * | Out-File “c:\computer_filtered.txt”

    Only self-signed certs should be located in this store. If there are any non self-signed, then they should be exported for backup and deleted from the trusted roost certificate authority store. Computer_filtered.txt should not have any certs listed in it.

    ClientAuthTrustMode controls how client cert are validated at S-Channel level.

    Value Trust Mode Description
    0 Machine Trust (default) Requires that the client certificate is issued by a certificate in the trusted issuers list.
    1 Exclusive Root Trust Requires that a client certificate is chained to a root certificate that is contained in the caller-specified trusted issuer store. The certificate must also be issued from the trusted issuers list.
    2 Exclusive CA Trust Requires that a client certificate is chained to an intermediate CA certificate or to a root certificate in the caller-specified trusted issuer store.

    Resolution
    If there are non-self signed certs in the trusted root store but we do not want to delete them, the work around would be setting this reg key and then rebooting the server.
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL ClientAuthTrustMode =dword:00000002

    Changing it to a value of 2 means the client cert has to chain to a intermediate CA cert or Root CA cert in the caller-specified trusted issuer store. Even if we have not specified a specifc cert trust list store (CTL store). In that case, we will still use the Trusted Root CA store. However, because value 2 states it can chain to a “intermediate CA cert” or “root certificate”, then we do not run into the issue of having subCA certs in the root CA store.

    The preferred method would be to keep the value to 0 and remove the unneeded certs from the Root CA store. However, if we feel that is not possible, then changing this to a value of 2 should fix this issue.
    From my research the only effect this has is, it allows the application / http.sys binding to specify a different trusted issuer store other then the Root CA store. In case we do not specify it, s-channel just bypasses the check to look for non-self-signed certs in the Root CA store.

    Other Information
    Client Authentication mapping
    https://msdn.microsoft.com/en-us/library/aa292114(v=vs.71).aspx
    https://www.iis.net/configreference/system.webserver/security/authentication/iisclientcertificatemappingauthentication
    https://support.microsoft.com/en-us/kb/907274
    https://technet.microsoft.com/en-us/library/cc770480(v=ws.10).aspx

    I hope this helps!

    Thanks
    Saurabh Somani

    WCF: Support for Wild Card Host Header at IIS 10 and above

    $
    0
    0

    IIS 10 came with new feature to support Wild Card Host Headers.
    https://docs.microsoft.com/en-us/iis/get-started/whats-new-in-iis-10/wildcard-host-header-support

    Does WCF support it?
    WCF does not support this and still need to be configured in old way by creating multiple IIS binding for desired host headers.

    Error we might see when browsing the WCF service:
    The protocol binding ‘X.X.X.X:80:*.XYZ.com’ is not valid for ‘http’. This might be because the port number is out of range.

    Explanation:
    WCF hosted on IIS internally calls ServiceHost class to initiate the service. Parameters needed to call it include URI object.
    The below MSDN indicates that the URI object’s HOST property should always be DNS Host Name or IP Address.
    https://msdn.microsoft.com/en-us/library/system.uri.host.aspx

    If we use Wild Card Host header, eventually WCF will fail to initialize:

    0:022> !DumpObj /d 0000019251191520
    Name: System.UriBuilder
    MethodTable: 00007ff94e851ad0
    EEClass: 00007ff94e4c0520
    Size: 96(0x60) bytes
    File: C:\windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
    Fields:
    MT Field Offset Type VT Attr Value Name
    00007ff950c424b8 4000418 54 System.Boolean 1 instance 1 m_changed
    00007ff950c716b8 4000419 8 System.String 0 instance 0000019051131420 m_fragment
    00007ff950c716b8 400041a 10 System.String 0 instance 00000192511914c8 m_host
    00007ff950c716b8 400041b 18 System.String 0 instance 0000019051131420 m_password
    00007ff950c716b8 400041c 20 System.String 0 instance 0000019251191688 m_path
    00007ff950c73e98 400041d 50 System.Int32 1 instance -1 m_port
    00007ff950c716b8 400041e 28 System.String 0 instance 0000019051131420 m_query
    00007ff950c716b8 400041f 30 System.String 0 instance 00000192511915d0 m_scheme
    00007ff950c716b8 4000420 38 System.String 0 instance 00000192d113cfc0 m_schemeDelimiter
    00007ff94e8a44e8 4000421 40 System.Uri 0 instance 0000000000000000 m_uri
    00007ff950c716b8 4000422 48 System.String 0 instance 0000019051131420 m_username

    0:022> !DumpObj /d 00000192511914c8
    Name: System.String
    MethodTable: 00007ff950c716b8
    EEClass: 00007ff9505f47a8
    Size: 56(0x38) bytes
    File: C:\windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
    String: *.localhost.com
    Fields:
    MT Field Offset Type VT Attr Value Name
    00007ff950c73e98 4000248 8 System.Int32 1 instance 15 m_stringLength
    00007ff950c728c8 4000249 c System.Char 1 instance 2a m_firstChar
    00007ff950c716b8 400024d 90 System.String 0 shared static Empty
    >> Domain:Value 0000018f50912ee0:NotInit 00000193dc66d530:NotInit < !clrstack
    OS Thread Id: 0x89c4 (22)
    Child SP IP Call Site
    0000000c2727e758 00007ff9648f3c58 [HelperMethodFrame: 0000000c2727e758]
    0000000c2727e840 00007ff94ef40731 System.Uri.CreateThis(System.String, Boolean, System.UriKind)
    0000000c2727e890 00007ff94e79184b System.UriBuilder.get_Uri()
    0000000c2727e8d0 00007ff94d1c52d8 System.ServiceModel.Channels.BaseUriWithWildcard..ctor(System.String, Int32, System.String, Int32, System.String, System.String)
    0000000c2727e940 00007ff94d1c57bd System.ServiceModel.Channels.BaseUriWithWildcard.CreateHostedUri(System.String, System.String, System.String)
    0000000c2727e9b0 00007ff95ccf1960 System.ServiceModel.Activation.HttpHostedTransportConfiguration.CreateTransportManagers()

    0:022> !DumpObj /d 0000019251193970
    Name: System.String
    MethodTable: 00007ff950c716b8
    EEClass: 00007ff9505f47a8
    Size: 118(0x76) bytes
    File: C:\windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
    String: Invalid URI: The hostname could not be parsed.
    Fields:
    MT Field Offset Type VT Attr Value Name
    00007ff950c73e98 4000248 8 System.Int32 1 instance 46 m_stringLength
    00007ff950c728c8 4000249 c System.Char 1 instance 49 m_firstChar
    00007ff950c716b8 400024d 90 System.String 0 shared static Empty
    >> Domain:Value 0000018f50912ee0:NotInit 00000193dc66d530:NotInit <<

    This is not purely a WCF API issue, rather this is a System.URI class usage issue which does not allow us to use wild card character. It only allows DNS Host Name or IP Address.

    Hopefully this will be supported with WCF over new framework releases.

    Thanks
    Saurabh Somani

    WCF: Consume WCF REST service by HttpClient

    $
    0
    0

    In a recent case, one of my customers requested how to consume the WCF REST Service by taking the help of System.Net.Http.HttpClient modules (introduced in .NET 4.5). I hope the following details would help in depth.

     

    Create a new WCF service application project named "RestService"

    • WCF REST service contract appears as the following.
    		namespace RestService
    		{
    		    [ServiceContract]
    		    public interface IService1
    		    {
    
    		        [OperationContract]
    		        [WebInvoke(Method ="GET", ResponseFormat = WebMessageFormat.Json,
    		            UriTemplate = "get/{value}")]
    		        string GetData(string value);
    
    		        [OperationContract]
    		        [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json,
    		            UriTemplate = "get/add")]
    		        string AddData(int value);
    
    		        [OperationContract]
    		        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json,
    		            UriTemplate = "GetCustomer/{customerId}")]
    		        Customer GetCustomer(string customerId);
    
    		        [OperationContract]
    		        [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json,
    		            UriTemplate = "UpdateCustomer")]
    		        List UpdateCustomer(Customer customer);
    		    }
    
    			[DataContract]
    			public class Customer
    			{
    			        [DataMember]
    			        public string Id { get; set; }
    			        [DataMember]
    			        public string FirstName { get; set; }
    			        [DataMember]
    			        public string LastName { get; set; }
    			        [DataMember]
    			        public string SSN { get; set; }
    			}
                     }
    

     

    • The WCF REST service implementation appears as the following.
    		namespace RestService
    		{
    		    public class Service1 : IService1
    		    {
    		        public static List customerList = null;
    
    		        public Service1()
    		        {
    		            customerList = new List();
    
    		            // Currently, it is statically loaded with dummy values.
    		            // However, it can be extended to populate from a data source (e.g. SQL).
    		            customerList.Add(new Customer
    		            {
    		                Id = "1111",
    		                FirstName = "John",
    		                LastName = "Doe",
    		                SSN = "451234590"
    		            });
    		            customerList.Add(new Customer
    		            {
    		                Id = "1112",
    		                FirstName = "Jason",
    		                LastName = "Rudd",
    		                SSN = "451234592"
    		            });
    		            customerList.Add(new Customer
    		            {
    		                Id = "1113",
    		                FirstName = "Daniel",
    		                LastName = "Cheng",
    		                SSN = "451234596"
    		            });
    		        }
    
    		        public string GetData(string value)
    		        {
    		            return string.Format("You entered: {0}", value);
    		        }
    
    		        public string AddData(int value)
    		        {
    		            return string.Format("You entered: {0}", value);
    		        }
    
    		        public Customer GetCustomer(string customerId)
    		        {
    		            //TODO: query your repo and pull customer details
    		            return customerList.Find(t => t.Id == customerId);
    		        }
    
    		        public List UpdateCustomer(Customer customer)
    		        {
    		            //TODO: update customer information in your repo
    		            var targetCustomer = customerList.Where(t => t.Id == customer.Id).FirstOrDefault();
    
    		            if (targetCustomer != null)
    		            {
    		                // no need to update cutomerId
    		                targetCustomer.FirstName = customer.FirstName;
    		                targetCustomer.LastName = customer.LastName;
    		                targetCustomer.SSN = customer.SSN;
    		            }
    
    		            return customerList;
    		        }
    		    }
                     }
    

     

    • Web.config should be updated like the following for WCF REST service hosting on IIS.
    		<system.serviceModel>
    		    <behaviors>
    		      <serviceBehaviors>
    		        <behavior>
    		          <serviceMetadata httpGetEnabled="true"/>
    		          <serviceDebug includeExceptionDetailInFaults="true"/>
    		        </behavior>
                	    <endpointBehaviors>
    		      <behavior name="ep">
    		        <webHttp helpEnabled="true"/>
    		      </behavior>
    		    </endpointBehaviors>
    		    </behaviors>
    		    <services>
    		      <service name="RestService.Service1">
    		        <endpoint address=""
    		                  binding="webHttpBinding" bindingConfiguration="webh" behaviorConfiguration="ep" contract="RestService.IService1"/>
    		      </service>
    		    </services>
    		    <bindings>
    			<!-- The following customBinding can be used if intention is to convert "webHttpBinding" to "customBinding" for some reason. -->
    		      <customBinding>
    		        <binding name="webHttpDeviceBinding">
    		          <webMessageEncoding>
    		            <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647"
    		              maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
    		          </webMessageEncoding>
    		          <httpTransport manualAddressing="true" maxBufferPoolSize="2147483647"
    		            maxReceivedMessageSize="2147483647" authenticationScheme="None"
    		            maxBufferSize="2147483647" maxPendingAccepts="15" transferMode="Buffered" />
    		        </binding>
    		      <webHttpBinding>
    		        <binding name="webh">
    		          <security mode="None" />
    		        </binding>
    		        <binding name="webHttpDeviceBinding" closeTimeout="00:10:00"
    		          openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00"
    		          maxBufferSize="2147483647" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647"
    		          transferMode="Buffered">
    		          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647"
    		            maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
    		          <security mode="None" />
    		        </binding>
    		      </webHttpBinding>
    		    </bindings>
    		    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
                 </system.serviceModel>
    

     

    Host the WCF REST service application on IIS

    • Create an application pool named "RestServicePool"
    • Right click on VS project "RestService" -> Go to "Web" section -> set server to "Local IIS"/ prepare your path and "Create Virtual Directory"
    • Application would be deployed on IIS
    • Select the application on IIS manager -> Go to "Advanced Settings" -> Set the application pool to your pool "RestServicePool"

     

    Check if you are able to read "metadata" of the WCF REST service

    Consume the REST service by HttpClient

    • Create a console application named "RestClient"
    • "Add Service Reference" to your service URL http://pupanda-win10.fareast.corp.microsoft.com/RestService/Service1.svc [reason to go for the plan of downloading proxy classes w.r.t. service classes, is to  ensure that custom classes can smoothly be "de-serialized" or "serialized"]
    • Go to your "Program.cs", update the definition with the following code snippet

     

            private static async Task PostResult()
    		        {
    		            HttpResponseMessage response = null;
    
    		            using (var client = new HttpClient())
    		            {
    		                var uri = new Uri("http://pupanda-win10.fareast.corp.microsoft.com/RestService/Service1.svc/get/add");
    		                var jsonRequest = JsonConvert.SerializeObject("123");
    		                var stringContent = new StringContent(jsonRequest, Encoding.UTF8, "application/json");
    		                response = await client.PostAsync(uri, stringContent);
    		            }
    
    		            Console.WriteLine(await response.Content.ReadAsStringAsync());
    		        }
    
    		        private static async Task GetResult()
    		        {
    		            string response = null;
    
    		            using (var client = new HttpClient())
    		            {
    		                var uri = new Uri("http://pupanda-win10.fareast.corp.microsoft.com/RestService/Service1.svc/get/123");
    		                response = await client.GetStringAsync(uri);
    		            }
    
    		            Console.WriteLine(response);
    		        }
    
    		        private static async Task GetCustomerResult()
    		        {
    		            string response = null;
    
    		            using (var client = new HttpClient())
    		            {
    		                var uri = new Uri("http://pupanda-win10.fareast.corp.microsoft.com/RestService/Service1.svc/GetCustomer/1113");
    		                response = await client.GetStringAsync(uri);
    		            }
    
    		            Console.WriteLine(response);
    		        }
    
    		        private static async Task PostCustomerResult()
    		        {
    		            HttpResponseMessage response = null;
    
    		            using (var client = new HttpClient())
    		            {
    		                var uri = new Uri("http://pupanda-win10.fareast.corp.microsoft.com/RestService/Service1.svc/UpdateCustomer");
    		                Customer toUpdateCustomer = new Customer {
    		                    Id = "1113",
    		                    FirstName = "Daniel",
    		                    LastName = "Gates",
    		                    SSN = "4512xx596"
    		                };
    		                var jsonRequest = JsonConvert.SerializeObject(toUpdateCustomer);
    		                var stringContent = new StringContent(jsonRequest, Encoding.UTF8, "application/json");
    		                response = await client.PostAsync(uri, stringContent);
    		            }
    
    		            Console.WriteLine(await response.Content.ReadAsStringAsync());
    		        }
    
    		        static void Main(string[] args)
    		        {
    		            Console.WriteLine("Press 1 for GET String/ Press 2 for POST String/ Press 3 for GET Customer/ Press 4 for POST Customer");
    		            var input = Console.ReadLine();
    		            switch (input)
    		            {
    		                case "1":
    		                    Console.Write("Results from GET operation: ");
    		                    Task.Run(() => GetResult());
    		                    break;
    		                case "2":
    		                    Console.Write("Results from POST operation: ");
    		                    Task.Run(() => PostResult());
    		                    break;
    		                case "3":
    		                    Console.Write("Results from GET Customer operation: ");
    		                    Task.Run(() => GetCustomerResult());
    		                    break;
    		                case "4":
    		                    Console.Write("Results from POST Customer operation: ");
    		                    Task.Run(() => PostCustomerResult());
    		                    break;
    		                default:
    		                    Console.WriteLine("wrong option");
    		                    break;
    		            }
    
    		            Console.ReadLine();
            }
    

     

    Self-host the WCF REST Service

    • Create a console application named "RestServiceSelfHost"
    • Add reference to the "RestService" project
    • Update the "App.config" with following content
    		<system.serviceModel>
    		    <services>
    		      <service name="RestService.Service1">
    		        <host>
    		          <baseAddresses>
    		            <add baseAddress="http://localhost:8080/myrestservice"/>
    		          </baseAddresses>
    		        </host>
    		        <endpoint address=""
    		                  binding="webHttpBinding"
    		                  behaviorConfiguration="epB"
    		                  contract="RestService.IService1"/>
    		      </service>
    		    </services>
    		    <behaviors>
    		      <endpointBehaviors>
    		        <behavior name="epB">
    		          <webHttp helpEnabled="true"/>
    		        </behavior>
    		      </endpointBehaviors>
    		    </behaviors>
    		</system.serviceModel>
    
    • Update the Program.cs with following content
    		static void Main(string[] args)
    		        {
    		            using (ServiceHost host = new ServiceHost(typeof(Service1)))
    		            {
    		                host.Open();
    
    		                Console.WriteLine("The service is ready at {0}", host.Description.Endpoints[0].Address);
    		                Console.WriteLine("Press  to stop the service.");
    		                Console.ReadLine();
    
    		                // Close the ServiceHost.
    		                //host.Close();
    		            }
                           }
    

     

     

    Consume the self-hosted WCF REST service by HttpClient

    • Create a console application named "RestServiceSelfHostClient"
    • Update your "Program.cs" with the following section of code
    		 static void Main(string[] args)
    		        {
    		            Console.Write("Results from GET operation: ");
    		            Task.Run(() => GetResult());
    
    		            Console.ReadLine();
    		        }
    
    		        private static async Task GetResult()
    		        {
    		            string response = null;
    
    		            using (var client = new HttpClient())
    		            {
    		                var uri = new Uri("http://localhost:8080/myrestservice/get/123");
    		                response = await client.GetStringAsync(uri);
    		            }
    
    		            Console.WriteLine(response);
    		        }
    

     

    Note

    I have uploaded the whole sample application for your reference in OneDrive. It is a one solution having all the above details.

    PBKDF2 .Net API does not exists with SHA256 implementation. Here PBKDF2 stands for “Password-Based Key Derivation Function 2”.

    $
    0
    0

    PBKDF2 .Net API does not exists with SHA256 implementation.

    This is true and we know that we have the Rfc2898DeriveBytes class which implements password-based key derivation functionality, PBKDF2, by using a pseudo-random number generator based on HMACSHA1.

    However PBKDF2 can be implemented using SHA256, SHA384, SHA512 by using the CNG API’s.

    See below for an implementation.

     

    using System;

    using System.Diagnostics;

    using System.Globalization;

    using System.Security;

    using System.Security.Cryptography;

    namespace Security.Cryptography

    {

    /// <summary>

    /// Set of hash algorithms that can be used with PBKDF2.

    /// Choosing, e.g., SHA-256, with compute PBKDF2 with HMAC-SHA256 as

    /// a PRF.

    /// </summary>

    public static class PBKDF2HashAlgorithm

    {

    public const string SHA1 = BCryptNative.AlgorithmName.Sha1;

    public const string SHA256 = BCryptNative.AlgorithmName.Sha256;

    public const string SHA384 = BCryptNative.AlgorithmName.Sha384;

    public const string SHA512 = BCryptNative.AlgorithmName.Sha512;

     

    public static bool ValidateHashName(string name)

    {

    if(name != SHA1 &&

    name != SHA256 &&

    name != SHA384 &&

    name != SHA512)

    {

    return false;

    }

    return true;

    }

    }

    /// <summary>

    /// Class containing the API for PBKDF2, a wrapper of the CNG/bcrypt.dll implementation.

    /// </summary>

    public static class BCryptPBKDF2

    {

    /// <summary>

    /// Compute the PBKDF2 function on the given inputs using the CNG implementation in the <c>BCryptKeyDerivation</c> API.

    /// </summary>

    /// <param name="hashName">The hash function to use, must be one of the strings in <seealso cref="PBKDF2HashAlgorithm"/>.</param>

    /// <param name="password">The password, as a byte array (i.e., without a string termination character).</param>

    /// <param name="salt">The salt, a cryptographically random value. Should be 16-bytes or longer.</param>

    /// <param name="cIterations">The number of iterations of PBKDF2 to apply.</param>

    /// <returns>The digest of the password (also sometimes called derived key).  The length of the digest

    /// will be equal to the length of the chosen hash function output.</returns>

    /// <remarks>

    /// See http://msdn.microsoft.com/en-us/library/windows/desktop/hh448506 for a description

    /// of the wrapped function.  Larger values of cIterations will cause the function to use more

    /// CPU time, and will also increase the workfactor for an attacker in a brute-force attack.

    /// </remarks>

    public static byte[] ComputeHash(string hashName, byte[] password, byte[] salt, Int64 cIterations)

    {

    if (cIterations < 1)

    throw new ArgumentException("Iteration count must be greater than zero.", "cIterations");

    if (salt == null)

    throw new ArgumentException("Salt must be non-null", "salt");

    if (password == null)

    throw new ArgumentException("Password must be non-null", "password");

     

    if(!PBKDF2HashAlgorithm.ValidateHashName(hashName))

    throw new ArgumentException("Invalid hash name for PBKDF2");

     

    byte[] digest = null;

     

    double vers = Environment.OSVersion.Version.Major + Environment.OSVersion.Version.Minor * 0.1;

     

    if(vers > 6.1)

    {

    // The BCryptKeyDerivation API is only supported on Win8/Server 2012 and above

    digest = BCryptNative.PBKDF2BCryptKeyDerivation(hashName, password, salt, (UInt64) cIterations);

    }

    else

    {

    // Fall back to BCryptDeriveKeyPBKDF2, which is roughly 2x slower on systems without the KeyDerivation API

    digest = BCryptNative.PBKDF2BCryptDeriveKeyPBKDF2(hashName, password, salt, (UInt64)cIterations);

    }

     

    return digest;

    }

     

    }

    }

     

    Now you have to P/invoke the BCrypt API's and calls PBKDF2 via the BCryptKeyDerivation API, where you can specify which of HMAC-SHA256, HMAC-SHA384 or HMAC-SHA512 to use.

    See full details below.

    // Copyright (c) Microsoft Corporation.  All rights reserved.

    using System;

    using System.Diagnostics;

    using System.Diagnostics.CodeAnalysis;

    using System.Runtime.CompilerServices;

    using System.Runtime.ConstrainedExecution;

    using System.Runtime.InteropServices;

    using System.Security;

    using System.Security.Cryptography;

    using System.Text;

    using Microsoft.Win32.SafeHandles;

     

    namespace Security.Cryptography

    {

    //

    // Public facing enumerations

    //

     

    /// <summary>

    ///     Padding modes

    /// </summary>

    [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags", Justification = "Public use of the enum is not as flags")]

    [SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Justification = "The native BCRYPT_PAD_NONE value is 1, not 0, and this is for interop.")]

    public enum AsymmetricPaddingMode

    {

    /// <summary>

    ///     No padding

    /// </summary>

    None = 1,                       // BCRYPT_PAD_NONE

     

    /// <summary>

    ///     PKCS #1 padding

    /// </summary>

    Pkcs1 = 2,                      // BCRYPT_PAD_PKCS1

     

    /// <summary>

    ///     Optimal Asymmetric Encryption Padding

    /// </summary>

    Oaep = 4,                       // BCRYPT_PAD_OAEP

     

    /// <summary>

    ///     Probabilistic Signature Scheme padding

    /// </summary>

    Pss = 8                         // BCRYPT_PAD_PSS

    }

     

    /// <summary>

    ///     Native wrappers for bcrypt CNG APIs.

    ///

    ///     The general pattern for this interop layer is that the BCryptNative type exports a wrapper method

    ///     for consumers of the interop methods.  This wrapper method puts a managed face on the raw

    ///     P/Invokes, by translating from native structures to managed types and converting from error

    ///     codes to exceptions.

    /// </summary>

    internal static class BCryptNative

    {

    //

    // Enumerations

    //

     

    /// <summary>

    ///     Well known algorithm names

    /// </summary>

    internal static class AlgorithmName

    {

    internal const string Aes = "AES";                          // BCRYPT_AES_ALGORITHM

    internal const string Rng = "RNG";                          // BCRYPT_RNG_ALGORITHM

    internal const string Rsa = "RSA";                          // BCRYPT_RSA_ALGORITHM

    internal const string TripleDes = "3DES";                   // BCRYPT_3DES_ALOGORITHM

    internal const string Sha1 = "SHA1";                        // BCRYPT_SHA1_ALGORITHM

    internal const string Sha256 = "SHA256";                    // BCRYPT_SHA256_ALGORITHM

    internal const string Sha384 = "SHA384";                    // BCRYPT_SHA384_ALGORITHM

    internal const string Sha512 = "SHA512";                    // BCRYPT_SHA512_ALGORITHM

    internal const string Pbkdf2 = "PBKDF2";                    // BCRYPT_PBKDF2_ALGORITHM

    }

     

    /// <summary>

    ///     Flags for BCryptOpenAlgorithmProvider

    /// </summary>

    [Flags]

    internal enum AlgorithmProviderOptions

    {

    None                = 0x00000000,

    HmacAlgorithm       = 0x00000008,                           // BCRYPT_ALG_HANDLE_HMAC_FLAG

    }

     

    /// <summary>

    ///     Flags for use with the BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO structure

    /// </summary>

    [Flags]

    internal enum AuthenticatedCipherModeInfoFlags

    {

    None                = 0x00000000,

    ChainCalls          = 0x00000001,                           // BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG

    InProgress          = 0x00000002,                           // BCRYPT_AUTH_MODE_IN_PROGRESS_FLAG

    }

     

    /// <summary>

    ///     Well known chaining modes

    /// </summary>

    internal static class ChainingMode

    {

    internal const string Cbc = "ChainingModeCBC";              // BCRYPT_CHAIN_MODE_CBC

    internal const string Ccm = "ChainingModeCCM";              // BCRYPT_CHAIN_MODE_CCM

    internal const string Cfb = "ChainingModeCFB";              // BCRYPT_CHAIN_MODE_CFB

    internal const string Ecb = "ChainingModeECB";              // BCRYPT_CHAIN_MODE_ECB

    internal const string Gcm = "ChainingModeGCM";              // BCRYPT_CHAIN_MODE_GCM

    }

     

    /// <summary>

    ///     Result codes from BCrypt APIs

    /// </summary>

    internal enum ErrorCode

    {

    Success = 0x00000000,                                       // STATUS_SUCCESS

    AuthenticationTagMismatch = unchecked((int)0xC000A002),     // STATUS_AUTH_TAG_MISMATCH

    BufferToSmall = unchecked((int)0xC0000023),                 // STATUS_BUFFER_TOO_SMALL

    }

     

    internal static class HashPropertyName

    {

    internal const string HashLength = "HashDigestLength";      // BCRYPT_HASH_LENGTH

    }

     

    /// <summary>

    ///     Magic numbers for different key blobs

    /// </summary>

    internal enum KeyBlobMagicNumber

    {

    RsaPublic = 0x31415352,                                     // BCRYPT_RSAPUBLIC_MAGIC

    RsaPrivate = 0x32415352,                                    // BCRYPT_RSAPRIVATE_MAGIC

    KeyDataBlob = 0x4d42444b,                                   // BCRYPT_KEY_DATA_BLOB_MAGIC

    }

     

    /// <summary>

    ///     Well known key blob tyes

    /// </summary>

    internal static class KeyBlobType

    {

    internal const string KeyDataBlob = "KeyDataBlob";                  // BCRYPT_KEY_DATA_BLOB

    internal const string RsaFullPrivateBlob = "RSAFULLPRIVATEBLOB";    // BCRYPT_RSAFULLPRIVATE_BLOB

    internal const string RsaPrivateBlob = "RSAPRIVATEBLOB";            // BCRYPT_RSAPRIVATE_BLOB

    internal const string RsaPublicBlob = "RSAPUBLICBLOB";              // BCRYPT_PUBLIC_KEY_BLOB

    }

     

    /// <summary>

    ///     Well known BCrypt object property names

    /// </summary>

    internal static class ObjectPropertyName

    {

    internal const string AuthTagLength = "AuthTagLength";      // BCRYPT_AUTH_TAG_LENGTH

    internal const string BlockLength = "BlockLength";          // BCRYPT_BLOCK_LENGTH

    internal const string ChainingMode = "ChainingMode";        // BCRYPT_CHAINING_MODE

    internal const string InitializationVector = "IV";          // BCRYPT_INITIALIZATION_VECTOR

    internal const string KeyLength = "KeyLength";              // BCRYPT_KEY_LENGTH

    internal const string ObjectLength = "ObjectLength";        // BCRYPT_OBJECT_LENGTH

    }

     

    /// <summary>

    /// BCrypt parameter types (used in parameter lists)

    /// </summary>

    internal enum ParameterTypes

    {

    KdfHashAlgorithm = 0x0,

    KdfSalt = 0xF,

    KdfIterationCount = 0x10

    }

     

    /// <summary>

    ///     Well known BCrypt provider names

    /// </summary>

    internal static class ProviderName

    {

    internal const string MicrosoftPrimitiveProvider = "Microsoft Primitive Provider";      // MS_PRIMITIVE_PROVIDER

    }

     

    //

    // Structures

    //

     

    [StructLayout(LayoutKind.Sequential)]

    [SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable", Justification = "The resouces lifetime is owned by the containing type - as a value type, the pointers will be copied and are not owned by the value type itself.")]

    internal struct BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO

    {

    internal int cbSize;

    internal int dwInfoVersion;

     

    [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Justification = "The handle is not owned by the value type")]

    internal IntPtr pbNonce;            // byte *

    internal int cbNonce;

     

    [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Justification = "The handle is not owned by the value type")]

    internal IntPtr pbAuthData;         // byte *

    internal int cbAuthData;

     

    [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Justification = "The handle is not owned by the value type")]

    internal IntPtr pbTag;              // byte *

    internal int cbTag;

     

    [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Justification = "The handle is not owned by the value type")]

    internal IntPtr pbMacContext;       // byte *

    internal int cbMacContext;

     

    internal int cbAAD;

    internal long cbData;

    internal AuthenticatedCipherModeInfoFlags dwFlags;

    }

     

    [StructLayout(LayoutKind.Sequential)]

    internal struct BCRYPT_KEY_DATA_BLOB

    {

    internal KeyBlobMagicNumber dwMagic;

    internal int dwVersion;

    internal int cbKeyData;

    }

     

    [StructLayout(LayoutKind.Sequential)]

    internal struct BCRYPT_KEY_LENGTHS_STRUCT

    {

    internal int dwMinLength;

    internal int dwMaxLength;

    internal int dwIncrement;

    }

     

    [StructLayout(LayoutKind.Sequential)]

    internal struct BCRYPT_OAEP_PADDING_INFO

    {

    [MarshalAs(UnmanagedType.LPWStr)]

    internal string pszAlgId;

     

    internal IntPtr pbLabel;

     

    internal int cbLabel;

    }

     

    [StructLayout(LayoutKind.Sequential)]

    internal struct BCRYPT_PKCS1_PADDING_INFO

    {

    [MarshalAs(UnmanagedType.LPWStr)]

    internal string pszAlgId;

    }

     

    [StructLayout(LayoutKind.Sequential)]

    internal struct BCRYPT_PSS_PADDING_INFO

    {

    [MarshalAs(UnmanagedType.LPWStr)]

    internal string pszAlgId;

     

    internal int cbSalt;

    }

     

    [StructLayout(LayoutKind.Sequential)]

    internal struct BCRYPT_RSAKEY_BLOB

    {

    internal KeyBlobMagicNumber Magic;

    internal int BitLength;

    internal int cbPublicExp;

    internal int cbModulus;

    internal int cbPrime1;

    internal int cbPrime2;

    }

     

    [StructLayout(LayoutKind.Sequential)]

    internal struct BCryptBuffer

    {

    internal int cbBuffer;

    internal int BufferType;

    internal IntPtr pvBuffer;       // PVOID

    }

     

    [StructLayout(LayoutKind.Sequential)]

    internal struct BCryptBufferDesc

    {

    internal int ulVersion;

    internal int cBuffers;

    internal IntPtr pBuffers;       // PBCryptBuffer

    }

     

    //

    // P/Invokes

    //

     

    [SuppressUnmanagedCodeSecurity]

    private static class UnsafeNativeMethods

    {

    [DllImport("bcrypt.dll")]

    internal static extern ErrorCode BCryptCreateHash(SafeBCryptAlgorithmHandle hAlgorithm,

    [Out] out SafeBCryptHashHandle hHash,

    IntPtr pbHashObject,              // byte *

    int cbHashObject,

    [In, MarshalAs(UnmanagedType.LPArray)]byte[] pbSecret,

    int cbSecret,

    int dwFlags);

     

    // Overload of BCryptDecrypt for use in standard decryption

    [DllImport("bcrypt.dll")]

    internal static extern ErrorCode BCryptDecrypt(SafeBCryptKeyHandle hKey,

    [In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

    int cbInput,

    IntPtr pPaddingInfo,

    [In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbIV,

    int cbIV,

    [Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput,

    int cbOutput,

    [Out] out int pcbResult,

    int dwFlags);

     

    // Overload of BCryptDecrypt for use with authenticated decryption

    [DllImport("bcrypt.dll")]

    internal static extern ErrorCode BCryptDecrypt(SafeBCryptKeyHandle hKey,

    [In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

    int cbInput,

    [In, Out] ref BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO pPaddingInfo,

    [In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbIV,

    int cbIV,

    [Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput,

    int cbOutput,

    [Out] out int pcbResult,

    int dwFlags);

     

    // Overload of BCryptEncrypt for use in standard encryption

    [DllImport("bcrypt.dll")]

    internal static extern ErrorCode BCryptEncrypt(SafeBCryptKeyHandle hKey,

    [In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

    int cbInput,

    IntPtr pPaddingInfo,

    [In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbIV,

    int cbIV,

    [Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput,

    int cbOutput,

    [Out] out int pcbResult,

    int dwFlags);

     

    // Overload of BCryptEncrypt for use with authenticated encryption

    [DllImport("bcrypt.dll")]

    internal static extern ErrorCode BCryptEncrypt(SafeBCryptKeyHandle hKey,

    [In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

    int cbInput,

    [In, Out] ref BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO pPaddingInfo,

    [In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbIV,

    int cbIV,

    [Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput,

    int cbOutput,

    [Out] out int pcbResult,

    int dwFlags);

     

    [DllImport("bcrypt.dll")]

    internal static extern ErrorCode BCryptFinishHash(SafeBCryptHashHandle hHash,

    [Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput,

    int cbOutput,

    int dwFlags);

     

    [DllImport("bcrypt.dll")]

    internal static extern ErrorCode BCryptGenRandom(SafeBCryptAlgorithmHandle hAlgorithm,

    [In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbBuffer,

    int cbBuffer,

    int dwFlags);

     

    [DllImport("bcrypt.dll", EntryPoint = "BCryptGetProperty")]

    internal static extern ErrorCode BCryptGetAlgorithmProperty(SafeBCryptAlgorithmHandle hObject,

    [MarshalAs(UnmanagedType.LPWStr)] string pszProperty,

    [In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput,

    int cbOutput,

    [In, Out] ref int pcbResult,

    int flags);

     

    [DllImport("bcrypt.dll", EntryPoint = "BCryptGetProperty")]

    internal static extern ErrorCode BCryptGetHashProperty(SafeBCryptHashHandle hObject,

    [MarshalAs(UnmanagedType.LPWStr)] string pszProperty,

    [In, Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbOutput,

    int cbOutput,

    [In, Out] ref int pcbResult,

    int flags);

     

    [DllImport("bcrypt.dll")]

    internal static extern ErrorCode BCryptHashData(SafeBCryptHashHandle hHash,

    [In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

    int cbInput,

    int dwFlags);

     

    [DllImport("bcrypt.dll")]

    internal static extern ErrorCode BCryptImportKey(SafeBCryptAlgorithmHandle hAlgorithm,

    IntPtr hImportKey,

    [MarshalAs(UnmanagedType.LPWStr)] string pszBlobType,

    [Out] out SafeBCryptKeyHandle phKey,

    [In, Out] IntPtr pbKeyObject,          // BYTE *

    int cbKeyObject,

    [In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

    int cbInput,

    int dwFlags);

     

    [DllImport("bcrypt.dll")]

    internal static extern ErrorCode BCryptOpenAlgorithmProvider([Out] out SafeBCryptAlgorithmHandle phAlgorithm,

    [MarshalAs(UnmanagedType.LPWStr)] string pszAlgId,

    [MarshalAs(UnmanagedType.LPWStr)] string pszImplementation,

    AlgorithmProviderOptions dwFlags);

     

    [DllImport("bcrypt.dll", EntryPoint = "BCryptSetProperty")]

    internal static extern ErrorCode BCryptSetAlgorithmProperty(SafeBCryptAlgorithmHandle hObject,

    [MarshalAs(UnmanagedType.LPWStr)] string pszProperty,

    [In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

    int cbInput,

    int dwFlags);

     

    [DllImport("bcrypt.dll", EntryPoint = "BCryptSetProperty")]

    internal static extern ErrorCode BCryptSetHashProperty(SafeBCryptHashHandle hObject,

    [MarshalAs(UnmanagedType.LPWStr)] string pszProperty,

    [In, MarshalAs(UnmanagedType.LPArray)] byte[] pbInput,

    int cbInput,

    int dwFlags);

     

    [DllImport("bcrypt.dll", EntryPoint = "BCryptKeyDerivation")]

    internal static extern ErrorCode BCryptKeyDerivation(SafeBCryptKeyHandle hKey,

    [In] ref BCryptBufferDesc pParamList,

    [Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbDerivedKey,

    int cbDerivedKey,

    [In, Out] ref int pcbResult,

    int dwFlags);

     

    [DllImport("bcrypt.dll", EntryPoint = "BCryptGenerateSymmetricKey")]

    internal static extern ErrorCode BCryptGenerateSymmetricKey(SafeBCryptAlgorithmHandle hAlgorithm,

    [Out] out SafeBCryptKeyHandle phKey,

    [Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbKeyObjectOptional,

    int cbKeyObject,

    [Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbSecret,

    int cbSecret,

    int dwFlags);

     

    [DllImport("bcrypt.dll", EntryPoint = "BCryptDeriveKeyPBKDF2")]

    internal static extern ErrorCode BCryptDeriveKeyPBKDF2(SafeBCryptAlgorithmHandle hPrf,

    [In, MarshalAs(UnmanagedType.LPArray)] byte[] pbPassword,

    int cbPassword,

    [In, MarshalAs(UnmanagedType.LPArray)] byte[] pbSalt,

    int cbSalt,

    ulong cIterations,

    [Out, MarshalAs(UnmanagedType.LPArray)] byte[] pbDerivedKey,

    int cbDerivedKey,

    int dwFlags);

     

    }

     

    /// <summary>

    ///     Adapter to wrap specific BCryptGetProperty P/Invokes with a generic BCrypt handle type

    /// </summary>

    [SecurityCritical]

    private delegate ErrorCode BCryptPropertyGetter<T>(T hObject,

    string pszProperty,

    byte[] pbOutput,

    int cbOutput,

    ref int pcbResult,

    int dwFlags) where T : SafeHandle;

     

    /// <summary>

    ///     Adapter to wrap specific BCryptSetProperty P/Invokes with a generic BCrypt handle type

    /// </summary>

    [SecurityCritical]

    private delegate ErrorCode BCryptPropertySetter<T>(T hObject,

    string pszProperty,

    byte[] pbInput,

    int cbInput,

    int dwFlags) where T : SafeHandle;

     

    //

    // Wrapper APIs

    //

     

    [SecurityCritical]

    internal static SafeBCryptHashHandle CreateHash(SafeBCryptAlgorithmHandle algorithm,

    byte[] secret)

    {

    Debug.Assert(algorithm != null, "algorithm != null");

    Debug.Assert(!algorithm.IsClosed && !algorithm.IsInvalid, "!algorithm.IsClosed && !algorithm.IsInvalid");

     

    IntPtr hashObject = IntPtr.Zero;

    SafeBCryptHashHandle hash = null;

     

    RuntimeHelpers.PrepareConstrainedRegions();

    try

    {

    // Figure out how big of a buffer is needed for the hash object and allocate it

    int hashObjectSize = GetInt32Property(algorithm, ObjectPropertyName.ObjectLength);

     

    RuntimeHelpers.PrepareConstrainedRegions();

    try { }

    finally

    {

    hashObject = Marshal.AllocCoTaskMem(hashObjectSize);

    }

     

    // Create the hash object

    ErrorCode error = UnsafeNativeMethods.BCryptCreateHash(algorithm,

    out hash,

    hashObject,

    hashObjectSize,

    secret,

    secret != null ? secret.Length : 0,

    0);

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

     

    // Transfer ownership of the buffer to the safe handle

    hash.DataBuffer = hashObject;

     

    return hash;

    }

    finally

    {

    // If the safe hash handle never took ownership of the data buffer, free it now.

    if (hashObject != IntPtr.Zero)

    {

    if (hash == null || hash.DataBuffer == IntPtr.Zero)

    {

    Marshal.FreeCoTaskMem(hashObject);

    }

    }

    }

    }

     

    /// <summary>

    ///     Get the results of a hashing operation

    /// </summary>

    [SecurityCritical]

    internal static byte[] FinishHash(SafeBCryptHashHandle hash)

    {

    Debug.Assert(hash != null, "hash != null");

    Debug.Assert(!hash.IsClosed && !hash.IsInvalid, "!hash.IsClosed && !hash.IsInvalid");

     

    int hashSize = GetInt32Property(hash, HashPropertyName.HashLength);

    byte[] result = new byte[hashSize];

     

    ErrorCode error = UnsafeNativeMethods.BCryptFinishHash(hash, result, result.Length, 0);

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

     

    return result;

    }

     

    /// <summary>

    ///     Fill a buffer with radom bytes

    /// </summary>

    [SecurityCritical]

    [SecuritySafeCritical]

    internal static void GenerateRandomBytes(SafeBCryptAlgorithmHandle algorithm, byte[] buffer)

    {

    Debug.Assert(algorithm != null, "algorithm != null");

    Debug.Assert(!algorithm.IsClosed && !algorithm.IsInvalid, "!algorithm.IsClosed && !algorithm.IsInvalid");

    Debug.Assert(buffer != null, "buffer != null");

     

    ErrorCode error = UnsafeNativeMethods.BCryptGenRandom(algorithm,

    buffer,

    buffer.Length,

    0);

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

    }

     

    /// <summary>

    ///     Get an integer valued named property from a BCrypt object.

    /// </summary>

    [SecurityCritical]

    internal static int GetInt32Property<T>(T bcryptObject, string property) where T : SafeHandle

    {

    Debug.Assert(bcryptObject != null, "bcryptObject != null");

    Debug.Assert(!bcryptObject.IsClosed && !bcryptObject.IsInvalid, "!bcryptObject.IsClosed && !bcryptObject.IsInvalid");

    Debug.Assert(!String.IsNullOrEmpty(property), "!String.IsNullOrEmpty(property)");

     

    return BitConverter.ToInt32(GetProperty(bcryptObject, property), 0);

    }

     

    /// <summary>

    ///     Get a string valued named property from a BCrypt object

    /// </summary>

    [SecurityCritical]

    internal static string GetStringProperty<T>(T bcryptObject, string property) where T : SafeHandle

    {

    Debug.Assert(bcryptObject != null, "bcryptObject != null");

    Debug.Assert(!bcryptObject.IsClosed && !bcryptObject.IsInvalid, "!bcryptObject.IsClosed && !bcryptObject.IsInvalid");

    Debug.Assert(!String.IsNullOrEmpty(property), "!String.IsNullOrEmpty(property)");

     

    byte[] rawProperty = GetProperty(bcryptObject, property);

     

    if (rawProperty == null)

    {

    return null;

    }

    else if (rawProperty.Length == 0)

    {

    return string.Empty;

    }

    else

    {

    unsafe

    {

    fixed (byte *pPropertyBytes = rawProperty)

    {

    return Marshal.PtrToStringUni(new IntPtr(pPropertyBytes));

    }

    }

    }

    }

     

    /// <summary>

    ///     Get a property from a BCrypt which is returned as a structure

    /// </summary>

    [SecurityCritical]

    [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Internal critical API")]

    internal static TProperty GetValueTypeProperty<THandle, TProperty>(THandle bcryptObject, string property)

    where THandle : SafeHandle

    where TProperty : struct

    {

    Debug.Assert(bcryptObject != null, "bcryptObject != null");

    Debug.Assert(!bcryptObject.IsClosed && !bcryptObject.IsInvalid, "!bcryptObject.IsClosed && !bcryptObject.IsInvalid");

    Debug.Assert(!String.IsNullOrEmpty(property), "!String.IsNullOrEmpty(property)");

     

    byte[] rawProperty = GetProperty(bcryptObject, property);

     

    if (rawProperty == null || rawProperty.Length == 0)

    {

    return default(TProperty);

    }

    else

    {

    Debug.Assert(Marshal.SizeOf(typeof(TProperty)) <= rawProperty.Length, "Unexpected property size");

    unsafe

    {

    fixed (byte* pPropertyBytes = rawProperty)

    {

    return (TProperty)Marshal.PtrToStructure(new IntPtr(pPropertyBytes), typeof(TProperty));

    }

    }

    }

    }

     

    /// <summary>

    ///     Get the value of a named property from a BCrypt object

    /// </summary>

    [SecurityCritical]

    internal static byte[] GetProperty<T>(T bcryptObject, string property) where T : SafeHandle

    {

    Debug.Assert(bcryptObject != null, "bcryptObject != null");

    Debug.Assert(!bcryptObject.IsClosed && !bcryptObject.IsInvalid, "!bcryptObject.IsClosed && !bcryptObject.IsInvalid");

    Debug.Assert(!String.IsNullOrEmpty(property), "!String.IsNullOrEmpty(property)");

     

    // Figure out which P/Invoke to use for the specific SafeHandle type we were given. For now we

    // only need to get properties of BCrypt algorithms, so we only check for SafeBCryptAlgorithmHandles.

    BCryptPropertyGetter<T> propertyGetter = null;

    if (typeof(T) == typeof(SafeBCryptAlgorithmHandle))

    {

    propertyGetter = new BCryptPropertyGetter<SafeBCryptAlgorithmHandle>(UnsafeNativeMethods.BCryptGetAlgorithmProperty) as BCryptPropertyGetter<T>;

    }

    else if (typeof(T) == typeof(SafeBCryptHashHandle))

    {

    propertyGetter = new BCryptPropertyGetter<SafeBCryptHashHandle>(UnsafeNativeMethods.BCryptGetHashProperty) as BCryptPropertyGetter<T>;

    }

     

    Debug.Assert(propertyGetter != null, "Unknown bcrypt object type");

     

    // Figure out how big of a buffer is needed to hold the property

    int propertySize = 0;

    ErrorCode error = propertyGetter(bcryptObject, property, null, 0, ref propertySize, 0);

    if (error != ErrorCode.Success && error != ErrorCode.BufferToSmall)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

     

    // Get the property value

    byte[] propertyValue = new byte[propertySize];

    error = propertyGetter(bcryptObject,

    property,

    propertyValue,

    propertyValue.Length,

    ref propertySize,

    0);

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

     

    return propertyValue;

    }

     

    /// <summary>

    ///     Add some data to a hash in progress

    /// </summary>

    [SecurityCritical]

    internal static void HashData(SafeBCryptHashHandle hash, byte[] data)

    {

    Debug.Assert(hash != null, "hash != null");

    Debug.Assert(!hash.IsClosed && !hash.IsInvalid, "!hash.IsClosed && !hash.IsInvalid");

    Debug.Assert(data != null, "data != null");

     

    ErrorCode error = UnsafeNativeMethods.BCryptHashData(hash, data, data.Length, 0);

     

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

    }

     

    /// <summary>

    ///     Import a raw symmetric key into a key handle

    /// </summary>

    [SecurityCritical]

    internal static SafeBCryptKeyHandle ImportSymmetricKey(SafeBCryptAlgorithmHandle algorithm, byte[] key)

    {

    Debug.Assert(algorithm != null, "algorithm != null");

    Debug.Assert(!algorithm.IsClosed && !algorithm.IsInvalid, "!algorithm.IsClosed && !algorithm.IsInvalid");

    Debug.Assert(key != null, "buffer != null");

     

    IntPtr keyDataBuffer = IntPtr.Zero;

    SafeBCryptKeyHandle keyHandle = null;

     

    RuntimeHelpers.PrepareConstrainedRegions();

    try

    {

    // Build up the key blob structure in memory.  BCryptImportKey requries a

    // BCRYPT_KEY_DATA_BLOB header immediately followed by the raw key data.

    byte[] keyBlob = new byte[Marshal.SizeOf(typeof(BCRYPT_KEY_DATA_BLOB)) + key.Length];

    unsafe

    {

    fixed (byte* pbKeyBlob = keyBlob)

    {

    BCRYPT_KEY_DATA_BLOB* pkeyDataBlob = (BCRYPT_KEY_DATA_BLOB*)pbKeyBlob;

    pkeyDataBlob->dwMagic = KeyBlobMagicNumber.KeyDataBlob;

    pkeyDataBlob->dwVersion = 1;

    pkeyDataBlob->cbKeyData = key.Length;

    }

    }

    Buffer.BlockCopy(key, 0, keyBlob, Marshal.SizeOf(typeof(BCRYPT_KEY_DATA_BLOB)), key.Length);

     

    // Figure out how big of a key data buffer we need and allocate space on the native heap for

    // it.  We cannot use a managed array here because the address needs to stay constant for

    // the lifetime of the algorithm handle.  Pinning for a potentially long lifetime is

    // undesirable, so we use a native heap allocation instead.

    int keyDataSize = GetInt32Property(algorithm, ObjectPropertyName.ObjectLength);

     

    RuntimeHelpers.PrepareConstrainedRegions();

    try { }

    finally

    {

    keyDataBuffer = Marshal.AllocCoTaskMem(keyDataSize);

    }

     

    // Import the key

    ErrorCode error = UnsafeNativeMethods.BCryptImportKey(algorithm,

    IntPtr.Zero,

    KeyBlobType.KeyDataBlob,

    out keyHandle,

    keyDataBuffer,

    keyDataSize,

    keyBlob,

    keyBlob.Length,

    0);

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

     

    // Give the key ownership of the key data buffer

    keyHandle.DataBuffer = keyDataBuffer;

     

    return keyHandle;

    }

    finally

    {

    // If we allocated a key data buffer, but never transfered ownership to the key handle, then

    // we need to free it now otherwise it will leak.

    if (keyDataBuffer != IntPtr.Zero)

    {

    if (keyHandle == null ||keyHandle.DataBuffer == IntPtr.Zero)

    {

    Marshal.FreeCoTaskMem(keyDataBuffer);

    }

    }

    }

    }

     

    /// <summary>

    ///     Initialize a BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO structure (in place of the

    ///     BCRYPT_INIT_AUTH_MODE_INFO macro)

    /// </summary>

    [SecurityCritical]

    [SecuritySafeCritical]

    internal static void InitializeAuthnenticatedCipherModeInfo(ref BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo)

    {

    authInfo.cbSize = Marshal.SizeOf(typeof(BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO));

    authInfo.dwInfoVersion = 1; // BCRYPT_INIT_AUTH_MODE_INFO_VERSION

    }

     

    /// <summary>

    ///     Map a managed cipher mode to a BCrypt chaining mode

    /// </summary>

    internal static string MapChainingMode(CipherMode mode)

    {

    switch (mode)

    {

    case CipherMode.CBC:

    return ChainingMode.Cbc;

    case CipherMode.CFB:

    return ChainingMode.Cfb;

    case CipherMode.ECB:

    return ChainingMode.Ecb;

    default:

    throw new ArgumentException(Properties.Resources.UnsupportedCipherMode, "mode");

    }

    }

     

    /// <summary>

    ///     Map a BCrypt chaining mode to a managed cipher mode

    /// </summary>

    internal static CipherMode MapChainingMode(string mode)

    {

    Debug.Assert(mode != null, "mode != null");

     

    if (String.Equals(mode, ChainingMode.Cbc, StringComparison.Ordinal))

    {

    return CipherMode.CBC;

    }

    else if (String.Equals(mode, ChainingMode.Cfb, StringComparison.Ordinal))

    {

    return CipherMode.CFB;

    }

    else if (String.Equals(mode, ChainingMode.Ecb, StringComparison.Ordinal))

    {

    return CipherMode.ECB;

    }

    else

    {

    throw new ArgumentException(Properties.Resources.UnsupportedCipherMode, "mode");

    }

    }

     

    /// <summary>

    ///     Open a handle to a BCrypt algorithm provider

    /// </summary>

    [SecurityCritical]

    internal static SafeBCryptAlgorithmHandle OpenAlgorithm(string algorithm, string implementation)

    {

    return OpenAlgorithm(algorithm, implementation, AlgorithmProviderOptions.None);

    }

     

    [SecurityCritical]

    internal static SafeBCryptAlgorithmHandle OpenAlgorithm(string algorithm,

    string implementation,

    AlgorithmProviderOptions options)

    {

    Debug.Assert(!String.IsNullOrEmpty(algorithm), "!String.IsNullOrEmpty(algorithm)");

    // Note that implementation may be NULL (in which case the default provider will be used)

     

    SafeBCryptAlgorithmHandle algorithmHandle = null;

    ErrorCode error = UnsafeNativeMethods.BCryptOpenAlgorithmProvider(out algorithmHandle,

    algorithm,

    implementation,

    options);

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

     

    return algorithmHandle;

    }

     

    /// <summary>

    ///     Set an integer valued property on a BCrypt object

    /// </summary>

    [SecurityCritical]

    internal static void SetInt32Property<T>(T bcryptObject, string property, int value) where T : SafeHandle

    {

    Debug.Assert(bcryptObject != null, "bcryptObject != null");

    Debug.Assert(!bcryptObject.IsClosed && !bcryptObject.IsInvalid, "!bcryptObject.IsClosed && !bcryptObject.IsInvalid");

    Debug.Assert(!String.IsNullOrEmpty(property), "!String.IsNullOrEmpty(property)");

     

    SetProperty(bcryptObject, property, BitConverter.GetBytes(value));

    }

     

    /// <summary>

    ///     Set a string valued property on a BCrypt object

    /// </summary>

    [SecurityCritical]

    internal static void SetStringProperty<T>(T bcryptObject, string property, string value) where T : SafeHandle

    {

    Debug.Assert(bcryptObject != null, "bcryptObject != null");

    Debug.Assert(!bcryptObject.IsClosed && !bcryptObject.IsInvalid, "!bcryptObject.IsClosed && !bcryptObject.IsInvalid");

    Debug.Assert(!String.IsNullOrEmpty(property), "!String.IsNullOrEmpty(property)");

    Debug.Assert(value != null, "value != null");

     

    SetProperty(bcryptObject, property, Encoding.Unicode.GetBytes(value));

    }

     

    /// <summary>

    ///     Set a named property value on a BCrypt object

    /// </summary>

    [SecurityCritical]

    internal static void SetProperty<T>(T bcryptObject, string property, byte[] value) where T : SafeHandle

    {

    Debug.Assert(bcryptObject != null, "bcryptObject != null");

    Debug.Assert(!bcryptObject.IsClosed && !bcryptObject.IsInvalid, "!bcryptObject.IsClosed && !bcryptObject.IsInvalid");

    Debug.Assert(!String.IsNullOrEmpty(property), "!String.IsNullOrEmpty(property)");

    Debug.Assert(value != null, "value != null");

     

    // Figure out which P/Invoke to use for the specific handle type we were given. For now we

    // only need to set properties of BCrypt algorithms, so we only check for SafeBCryptAlgorithmHandles.

    BCryptPropertySetter<T> propertySetter = null;

    if (typeof(T) == typeof(SafeBCryptAlgorithmHandle))

    {

    propertySetter = new BCryptPropertySetter<SafeBCryptAlgorithmHandle>(UnsafeNativeMethods.BCryptSetAlgorithmProperty) as BCryptPropertySetter<T>;

    }

    else if (typeof(T) == typeof(SafeBCryptHashHandle))

    {

    propertySetter = new BCryptPropertySetter<SafeBCryptHashHandle>(UnsafeNativeMethods.BCryptSetHashProperty) as BCryptPropertySetter<T>;

    }

     

    Debug.Assert(propertySetter != null, "Unknown object type");

     

    // Set the property

    ErrorCode error = propertySetter(bcryptObject,

    property,

    value,

    value.Length,

    0);

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

    }

     

    /// <summary>

    ///     Decrypt some blocks of data

    /// </summary>

    [SecurityCritical]

    [SecuritySafeCritical]

    internal static byte[] SymmetricDecrypt(SafeBCryptKeyHandle key, byte[] iv, byte[] input)

    {

    Debug.Assert(key != null, "key != null");

    Debug.Assert(!key.IsClosed && !key.IsInvalid, "!key.IsClosed && !key.IsInvalid");

    Debug.Assert(input != null, "input != null");

     

    // Do the decryption

    byte[] output = new byte[input.Length];

    int outputSize = 0;

    ErrorCode error = UnsafeNativeMethods.BCryptDecrypt(key,

    input,

    input.Length,

    IntPtr.Zero,

    iv,

    iv != null ? iv.Length : 0,

    output,

    output.Length,

    out outputSize,

    0);

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

     

    // If we didn't use the whole output array, trim down to the portion that was used

    if (outputSize != output.Length)

    {

    byte[] trimmedOutput = new byte[outputSize];

    Buffer.BlockCopy(output, 0, trimmedOutput, 0, trimmedOutput.Length);

    output = trimmedOutput;

    }

     

    return output;

    }

     

    /// <summary>

    ///     Decrypt some blocks of data using authentication info

    /// </summary>

    [SecurityCritical]

    internal static byte[] SymmetricDecrypt(SafeBCryptKeyHandle key,

    byte[] input,

    byte[] chainData,

    ref BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authenticationInfo)

    {

    Debug.Assert(key != null, "key != null");

    Debug.Assert(!key.IsClosed && !key.IsInvalid, "!key.IsClosed && !key.IsInvalid");

     

    // Do the decryption

    byte[] output = new byte[input != null ? input.Length : 0];

    int outputSize = 0;

    ErrorCode error = UnsafeNativeMethods.BCryptDecrypt(key,

    input,

    input != null ? input.Length : 0,

    ref authenticationInfo,

    chainData,

    chainData != null ? chainData.Length : 0,

    output,

    output.Length,

    out outputSize,

    0);

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

     

    // If we didn't use the whole output array, trim down to the portion that was used

    if (outputSize != output.Length)

    {

    byte[] trimmedOutput = new byte[outputSize];

    Buffer.BlockCopy(output, 0, trimmedOutput, 0, trimmedOutput.Length);

    output = trimmedOutput;

    }

     

    return output;

    }

     

    /// <summary>

    ///     Encrypt some blocks of data

    /// </summary>

    [SecurityCritical]

    [SecuritySafeCritical]

    internal static byte[] SymmetricEncrypt(SafeBCryptKeyHandle key, byte[] iv, byte[] input)

    {

    Debug.Assert(key != null, "key != null");

    Debug.Assert(!key.IsClosed && !key.IsInvalid, "!key.IsClosed && !key.IsInvalid");

    Debug.Assert(input != null, "input != null");

     

    // Do the encryption

    byte[] output = new byte[input.Length];

    int outputSize = 0;

    ErrorCode error = UnsafeNativeMethods.BCryptEncrypt(key,

    input,

    input != null ? input.Length : 0,

    IntPtr.Zero,

    iv,

    iv != null ? iv.Length : 0,

    output,

    output.Length,

    out outputSize,

    0);

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

     

    // If we didn't use the whole output array, trim down to the portion that was used

    if (outputSize != output.Length)

    {

    byte[] trimmedOutput = new byte[outputSize];

    Buffer.BlockCopy(output, 0, trimmedOutput, 0, trimmedOutput.Length);

    output = trimmedOutput;

    }

     

    return output;

    }

     

    /// <summary>

    ///     Encrypt some blocks of data using authentication information

    /// </summary>

    [SecurityCritical]

    [SecuritySafeCritical]

    internal static byte[] SymmetricEncrypt(SafeBCryptKeyHandle key,

    byte[] input,

    byte[] chainData,

    ref BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authenticationInfo)

    {

    Debug.Assert(key != null, "key != null");

    Debug.Assert(!key.IsClosed && !key.IsInvalid, "!key.IsClosed && !key.IsInvalid");

     

    // Do the encryption

    byte[] output = new byte[input != null ? input.Length : 0];

    int outputSize = 0;

    ErrorCode error = UnsafeNativeMethods.BCryptEncrypt(key,

    input,

    input != null ? input.Length : 0,

    ref authenticationInfo,

    chainData,

    chainData != null ? chainData.Length : 0,

    output,

    output.Length,

    out outputSize,

    0);

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

     

    // If we didn't use the whole output array, trim down to the portion that was used

    if (outputSize != output.Length)

    {

    byte[] trimmedOutput = new byte[outputSize];

    Buffer.BlockCopy(output, 0, trimmedOutput, 0, trimmedOutput.Length);

    output = trimmedOutput;

    }

     

    return output;

    }

     

    /// <summary>

    ///     Calls PBKDF2 via the <c>BCryptKeyDerivation</c> API.  <param name="hashName"/> specifies which of HMAC-SHA256, HMAC-SHA384 or HMAC-SHA512 to use.

    ///     See the <see cref="AlgorithmName"/> class for supported hash functions.  <param name="password"/> is the password, and <param name="salt"/>

    ///     is the salt and <param name="iterations"/> is the iteration count.

    /// </summary>

    [SecurityCritical]

    [SecuritySafeCritical]

    internal static byte[] PBKDF2BCryptKeyDerivation(string hashName,

    byte[] password,

    byte[] salt,

    ulong iterations)

    {

    // Open a hash object to get the digest length

    SafeBCryptAlgorithmHandle hPrf = OpenAlgorithm(hashName, null);

    int hashLength = GetInt32Property(hPrf, HashPropertyName.HashLength);

    hPrf.Close();

     

    // Create a "key" object from the password

    SafeBCryptAlgorithmHandle hPbkdf2 = OpenAlgorithm(AlgorithmName.Pbkdf2, null);

     

    SafeBCryptKeyHandle hKey = new SafeBCryptKeyHandle();

    ErrorCode error = UnsafeNativeMethods.BCryptGenerateSymmetricKey(hPbkdf2, out hKey, null, 0, password, password.Length, 0);

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

    hPbkdf2.Close();

     

    // Prepare the param buffer

    BCryptBuffer[] buffer = new BCryptBuffer[3];

    buffer[0].BufferType = (int) ParameterTypes.KdfHashAlgorithm;

    buffer[0].cbBuffer = hashName.Length*2;                 // *2 since a WCHAR is 2-bytes

    buffer[0].pvBuffer = Marshal.StringToCoTaskMemUni(hashName);

     

    buffer[1].BufferType = (int) ParameterTypes.KdfSalt;

    buffer[1].cbBuffer = salt.Length;

    buffer[1].pvBuffer = Marshal.AllocCoTaskMem(salt.Length);

    Marshal.Copy(salt, 0, buffer[1].pvBuffer, salt.Length);

     

    buffer[2].BufferType = (int) ParameterTypes.KdfIterationCount;

    buffer[2].cbBuffer = sizeof(ulong);

    buffer[2].pvBuffer = Marshal.AllocCoTaskMem(buffer[2].cbBuffer);

    Marshal.Copy(BitConverter.GetBytes(iterations), 0, buffer[2].pvBuffer, buffer[2].cbBuffer);

     

    BCryptBufferDesc pParamList = new BCryptBufferDesc();

    pParamList.ulVersion = 0;       //BCRYPTBUFFER_VERSION

    pParamList.cBuffers = 3;

    GCHandle gch = GCHandle.Alloc(buffer, GCHandleType.Pinned);

    pParamList.pBuffers = gch.AddrOfPinnedObject();

     

    // Derive the key

    byte[] derivedKey = new byte[hashLength];

    int pcbResult = 0;

    error = UnsafeNativeMethods.BCryptKeyDerivation(hKey, ref pParamList, derivedKey, derivedKey.Length, ref pcbResult, 0);

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

    if(pcbResult != hashLength)

    {

    throw new CryptographicException("Invalid result from BCryptKeyDerivation (PBKDF2).  Derived key is too short.");

    }

     

    hKey.Close();

    Marshal.FreeCoTaskMem(buffer[0].pvBuffer);

    Marshal.FreeCoTaskMem(buffer[1].pvBuffer);

    Marshal.FreeCoTaskMem(buffer[2].pvBuffer);

    gch.Free();

     

    return derivedKey;

    }

     

     

     

    /// <summary>

    ///     Call PBKDF2 via the BCryptDeriveKeyPBKDF2 API. <param name="hashName"/> specifies which of HMAC-SHA256, HMAC-SHA384 or HMAC-SHA512 to use

    ///     See the <see cref="AlgorithmName"/> class for supported hash functions. <param name="password"/> is the password, and <param name="salt"/>

    ///     is the salt and <param name="iterations"/> is the iteration count.

    /// </summary>

    [SecurityCritical]

    [SecurityTreatAsSafe]

    internal static byte[] PBKDF2BCryptDeriveKeyPBKDF2(string hashName,

    byte[] password,

    byte[] salt,

    ulong iterations)

    {

    // Open handle for HMAC-hashName, with the default algorithm provider

    SafeBCryptAlgorithmHandle hPrf = OpenAlgorithm(hashName, null, AlgorithmProviderOptions.HmacAlgorithm);

     

    // Get the hash length

    int hashLength = GetInt32Property(hPrf, HashPropertyName.HashLength);

    byte[] derivedKey = new byte[hashLength];

     

    ErrorCode error = UnsafeNativeMethods.BCryptDeriveKeyPBKDF2(hPrf, password, password.Length, salt, salt.Length, iterations, derivedKey, derivedKey.Length, 0);

    if (error != ErrorCode.Success)

    {

    throw new CryptographicException(Win32Native.GetNTStatusMessage((int)error));

    }

     

    hPrf.Close();

     

    return derivedKey;

    }

     

     

    }       // end class BCryptNative

     

    /// <summary>

    ///     SafeHandle for a native BCRYPT_ALG_HANDLE

    /// </summary>

    internal sealed class SafeBCryptAlgorithmHandle : SafeHandleZeroOrMinusOneIsInvalid

    {

    private SafeBCryptAlgorithmHandle() : base(true)

    {

    return;

    }

     

    [DllImport("bcrypt.dll")]

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

    [SuppressUnmanagedCodeSecurity]

    [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "SafeHandle release P/Invoke")]

    private static extern BCryptNative.ErrorCode BCryptCloseAlgorithmProvider(IntPtr hAlgorithm, int flags);

     

    protected override bool ReleaseHandle()

    {

    return BCryptCloseAlgorithmProvider(handle, 0) == BCryptNative.ErrorCode.Success;

    }

    }

     

    /// <summary>

    ///     SafeHandle for a BCRYPT_HASH_HANDLE.

    /// </summary>

    internal sealed class SafeBCryptHashHandle : SafeHandleWithBuffer

    {

    [DllImport("bcrypt.dll")]

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

    [SuppressUnmanagedCodeSecurity]

    [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "SafeHandle release P/Invoke")]

    private static extern BCryptNative.ErrorCode BCryptDestroyHash(IntPtr hHash);

     

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

    protected override bool ReleaseNativeHandle()

    {

    return BCryptDestroyHash(handle) == BCryptNative.ErrorCode.Success;

    }

    }

     

    /// <summary>

    ///     SafeHandle for a native BCRYPT_KEY_HANDLE.

    /// </summary>

    internal sealed class SafeBCryptKeyHandle : SafeHandleWithBuffer

    {

    [DllImport("bcrypt.dll")]

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

    [SuppressUnmanagedCodeSecurity]

    [SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass", Justification = "SafeHandle release P/Invoke")]

    private static extern BCryptNative.ErrorCode BCryptDestroyKey(IntPtr hKey);

     

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]

    protected override bool ReleaseNativeHandle()

    {

    return BCryptDestroyKey(handle) == BCryptNative.ErrorCode.Success;

    }

    }

    }

     

    More Info:

    + Presently there are no plans as of .NET 4.7 to implement PBKDF2 with SHA256 or above.

    + Having said this, there are proposals that in future versions of .NET framework, SHA256 & above implementations can be done.

    Microsoft.AspNetCore has implementations of PBKDF2. Please see https://github.com/aspnet/DataProtection/tree/dev/src/Microsoft.AspNetCore.Cryptography.KeyDerivation/PBKDF2 for a reference.

    I built a sample PbkDf2 class that has the implementations of: HMACSHA1, HMACSHA256 & HMACSHA512.

     

    using System;

    using System.Diagnostics;

    using System.Security.Cryptography;

    using System.Text;

     

    namespace PBKDF2

    {

    public enum KeyDerivationPrf { HMACSHA1, HMACSHA256, HMACSHA512 };

    internal interface IPbkdf2Provider

    {

    byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested);

    }

     

    internal sealed class ManagedPbkdf2Provider : IPbkdf2Provider

    {

    public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)

    {

    Debug.Assert(password != null);

    Debug.Assert(salt != null);

    Debug.Assert(iterationCount > 0);

    Debug.Assert(numBytesRequested > 0);

     

    // PBKDF2 is defined in NIST SP800-132, Sec. 5.3.

    // http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf

     

    byte[] retVal = new byte[numBytesRequested];

    int numBytesWritten = 0;

    int numBytesRemaining = numBytesRequested;

     

    // For each block index, U_0 := Salt || block_index

    byte[] saltWithBlockIndex = new byte[checked(salt.Length + sizeof(uint))];

    Buffer.BlockCopy(salt, 0, saltWithBlockIndex, 0, salt.Length);

     

    using (var hashAlgorithm = PrfToManagedHmacAlgorithm(prf, password))

    {

    for (uint blockIndex = 1; numBytesRemaining > 0; blockIndex++)

    {

    // write the block index out as big-endian

    saltWithBlockIndex[saltWithBlockIndex.Length - 4] = (byte)(blockIndex >> 24);

    saltWithBlockIndex[saltWithBlockIndex.Length - 3] = (byte)(blockIndex >> 16);

    saltWithBlockIndex[saltWithBlockIndex.Length - 2] = (byte)(blockIndex >> 8);

    saltWithBlockIndex[saltWithBlockIndex.Length - 1] = (byte)blockIndex;

     

    // U_1 = PRF(U_0) = PRF(Salt || block_index)

    // T_blockIndex = U_1

    byte[] U_iter = hashAlgorithm.ComputeHash(saltWithBlockIndex); // this is U_1

    byte[] T_blockIndex = U_iter;

     

    for (int iter = 1; iter < iterationCount; iter++)

    {

    U_iter = hashAlgorithm.ComputeHash(U_iter);

    XorBuffers(src: U_iter, dest: T_blockIndex);

    // At this point, the 'U_iter' variable actually contains U_{iter+1} (due to indexing differences).

    }

     

    // At this point, we're done iterating on this block, so copy the transformed block into retVal.

    int numBytesToCopy = Math.Min(numBytesRemaining, T_blockIndex.Length);

    Buffer.BlockCopy(T_blockIndex, 0, retVal, numBytesWritten, numBytesToCopy);

    numBytesWritten += numBytesToCopy;

    numBytesRemaining -= numBytesToCopy;

    }

    }

     

    // retVal := T_1 || T_2 || ... || T_n, where T_n may be truncated to meet the desired output length

    return retVal;

    }

     

    private static KeyedHashAlgorithm PrfToManagedHmacAlgorithm(KeyDerivationPrf prf, string password)

    {

    byte[] passwordBytes = Encoding.UTF8.GetBytes(password);

    try

    {

    switch (prf)

    {

    case KeyDerivationPrf.HMACSHA1:

    return new HMACSHA1(passwordBytes);

    case KeyDerivationPrf.HMACSHA256:

    return new HMACSHA256(passwordBytes);

    case KeyDerivationPrf.HMACSHA512:

    return new HMACSHA512(passwordBytes);

    default:

    throw new CryptographicException("Unrecognized PRF.");

    }

    }

    finally

    {

    // The HMAC ctor makes a duplicate of this key; we clear original buffer to limit exposure to the GC.

    Array.Clear(passwordBytes, 0, passwordBytes.Length);

    }

    }

     

    private static void XorBuffers(byte[] src, byte[] dest)

    {

    // Note: dest buffer is mutated.

    Debug.Assert(src.Length == dest.Length);

    for (int i = 0; i < src.Length; i++)

    {

    dest[i] ^= src[i];

    }

    }

    }

    }

     

    CryptoConfig is not able to identify HashPbkdf2 from the machine.config file, which is set as:

    $
    0
    0
    To know more about CryptoConfig please refer to https://msdn.microsoft.com/en-us/library/system.security.cryptography.cryptoconfig(v=vs.110).aspx
    It's a class that accesses the cryptography configuration information.
    Please refer to https://blogs.msdn.microsoft.com/shawnfa/2008/12/02/cryptoconfig/ to know more on CryptoConfig.
    Recently I encountered an issue where a customer was implementing his own HashPbkdf2 class that does the SHA256 hashing based on Password-Based Key Derivation Function 2.
    A theoritical reference to PBKDF2 can be found here: https://en.wikipedia.org/wiki/PBKDF2
    In .NET the Rfc2898DeriveBytes class implements PBKDF2, by using a pseudo-random number generator based on HMACSHA1. However this class does not implement SHA256 or SHA512.
    The issue is how to implement the cryptography configuration information correctly, so that your implementation of the class can take effect on all flavours of the .NET framework when you do a HashAlgorithm.Create() providing your class information as an input.
    For example if you implement the following:
    A. Class: HashPbkdf2
    B. Assembly: ClassLib.DLL
    The implementation of cryptography configuration information in machine.config should look like:
    <configuration>
    <mscorlib>
    <cryptographySettings>
    <cryptoNameMapping>
    <cryptoClasses>
    <cryptoClass PKDF2Hashing="HashPbkdf2, ClassLib,
    Culture=neutral, PublicKeyToken=69b7085a78ef8580, Version=1.0.0.0"/>
    </cryptoClasses>
    <nameEntry name="PKDF2" class="PKDF2Hashing"/>
    <nameEntry name="HashPbkdf2" class="PKDF2Hashing"/>
    <nameEntry name="ClassLib.HashPbkdf2" class="PKDF2Hashing"/>
    </cryptoNameMapping>
    </cryptographySettings>
    <SNIP>
    File: C:\Windows\Microsoft.NET\Framework\vX.X.XXXXX\CONFIG
    Here is a sample code to see that the above configuration works:
    private static string EncodePassword(string pass, string salt)
    {
    byte[] bIn = Encoding.Unicode.GetBytes(pass);
    byte[] bSalt = Convert.FromBase64String(salt);
    byte[] bRet = null;
                // user defined PBKDF2 custom algorithm
    // CryptoConfig looks for information in the configuration/mscorlib/cryptographySettings element of machine.config.
    // If there are multiple mscorlib sections, then crypto config prefers one with a version attribute that matches the current runtime
                HashAlgorithm hm = HashAlgorithm.Create("PKDF2");
                if (hm == null)
    {
    return "HashAlgorithm cannot be NULL. Exception";
    }
                if (hm is KeyedHashAlgorithm)
    {
    KeyedHashAlgorithm kha = (KeyedHashAlgorithm)hm;
    if (kha.Key.Length == bSalt.Length)
    {
    kha.Key = bSalt;
    }
    else if (kha.Key.Length < bSalt.Length)
    {
    byte[] bKey = new byte[kha.Key.Length];
    Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length);
    kha.Key = bKey;
    }
    else
    {
    byte[] bKey = new byte[kha.Key.Length];
    for (int iter = 0; iter < bKey.Length;)
    {
    int len = Math.Min(bSalt.Length, bKey.Length - iter);
    Buffer.BlockCopy(bSalt, 0, bKey, iter, len);
    iter += len;
    }
    kha.Key = bKey;
    }
    bRet = kha.ComputeHash(bIn);
    }
    else
    {
    byte[] bAll = new byte[bSalt.Length + bIn.Length];
    Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
    Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
    bRet = hm.ComputeHash(bAll);
    }
                return Convert.ToBase64String(bRet);
    }
    There is a 2nd way to solve this by not adding an entry to "machine.config" file. We can add the PKDF2 nameEntry to the current appdomain, so that the HashAlgorithm can be created at runtime.
    Code below for clarity.
    This is the custom class for adding the algo to the current AppDomain.
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq.Expressions;
    using System.Security;
    using System.Security.Cryptography;
    using System.Security.Permissions;
    using System.Threading;
    namespace TestPBKDF2
    {
    public static class MyCryptoConfig
    {
    private static Dictionary<string, Type> s_algorithmMap = DefaultAlgorithmMap;
    private static ReaderWriterLockSlim s_algorithmMapLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
            /// <summary>
    ///     Default mapping of algorithm names to algorithm types
    /// </summary>
    private static Dictionary<string, Type> DefaultAlgorithmMap
    {
    get
    {
    Dictionary<string, Type> map = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
                    //
    // System.Core algorithms
    //
                    AddAlgorithmToMap(map, typeof(AesCryptoServiceProvider), "AES");
    AddAlgorithmToMap(map, typeof(AesManaged));
                    AddAlgorithmToMap(map, typeof(ECDsaCng), "ECDsa");
                    AddAlgorithmToMap(map, typeof(ECDiffieHellmanCng), "ECDH", "ECDiffieHellman");
                    AddAlgorithmToMap(map, typeof(MD5Cng));
    AddAlgorithmToMap(map, typeof(SHA1Cng));
    AddAlgorithmToMap(map, typeof(SHA256Cng));
    AddAlgorithmToMap(map, typeof(SHA256CryptoServiceProvider));
    AddAlgorithmToMap(map, typeof(SHA384Cng));
    AddAlgorithmToMap(map, typeof(SHA384CryptoServiceProvider));
    AddAlgorithmToMap(map, typeof(SHA512Cng));
    AddAlgorithmToMap(map, typeof(SHA512CryptoServiceProvider));
                    return map;
    }
    }
            /// <summary>
    ///     <para>
    ///         AddAlgorithm allows an application to register a new algorithm with CryptoConfig2 in the
    ///         current AppDomain. The algorithm is then creatable via calling
    ///         <see cref="CreateFromName" /> and supplying one of:
    ///     </para>
    ///     <list type="bullet">
    ///         <item>The name of the algorithm type</item>
    ///         <item>The namespace qualified name of the algorithm type</item>
    ///         <item>Any of the aliases supplied for the type</item>
    ///     </list>
    ///     <para>
    ///         This registration is valid only in the AppDomain that does the registration, and is not
    ///         persisted. The registered algorithm will only be creatable via CryptoConfig2 and not via
    ///         standard <see cref="CryptoConfig" />.
    ///     </para>
    ///     <para>
    ///         All algorithms registered with CryptoConfig2 must have a default constructor, or they wil
    ///          not be creatable at runtime.
    ///     </para>
    ///     <para>
    ///         This method is thread safe.
    ///     </para>
    /// </summary>
    /// <permission cref="PermissionSet">The immediate caller of this API must be fully trusted</permission>
    /// <param name="algorithm">type to register with CryptoConfig2</param>
    /// <param name="aliases">list of additional aliases which can create the type</param>
    /// <exception cref="ArgumentNullException">
    ///     if <paramref name="algorithm"/> or <paramref name="aliases"/> are null
    /// </exception>
    /// <exception cref="InvalidOperationException">
    ///     if an alias is either null, empty, or a duplicate of an existing registered alias
    /// </exception>
    [PermissionSet(SecurityAction.LinkDemand, Unrestricted = true)]
    [SecurityCritical]
    public static void AddAlgorithm(Type algorithm, params string[] aliases)
    {
    if (algorithm == null)
    throw new ArgumentNullException("algorithm");
    if (aliases == null)
    throw new ArgumentNullException("aliases");
                s_algorithmMapLock.EnterWriteLock();
    try
    {
    // Make sure that we don't already have mappings for the input aliases - we want to eagerly
    // check for this rather than just letting the hash table insert fail so that the map doesn't
    // end up with some of the aliases added and others not added.
    //
    // Note that we're explicitly not trying to protect against having the same alias added
    // multiple times via the same call to AddAlgorithm, since that problem is detectable by the
    // user of the API whereas detecting a conflict with another alias which had been previously
    // added cannot be reliably detected in the presense of multiple threads.
    foreach (string alias in aliases)
    {
    if (String.IsNullOrEmpty(alias))
    {
    throw new InvalidOperationException("EmptyCryptoConfigAlias");
    }
                        if (s_algorithmMap.ContainsKey(alias))
    {
    throw new InvalidOperationException("DuplicateCryptoConfigAlias");
    }
    }
                    AddAlgorithmToMap(s_algorithmMap, algorithm, aliases);
    }
    finally
    {
    s_algorithmMapLock.ExitWriteLock();
    }
    }
            /// <summary>
    ///     Add an algorithm to a given type map
    /// </summary>
    private static void AddAlgorithmToMap(Dictionary<string, Type> map, Type algorithm, params string[] aliases)
    {
    Debug.Assert(map != null, "map != null");
    Debug.Assert(algorithm != null, "algorithm != null");
                foreach (string alias in aliases)
    {
    Debug.Assert(!String.IsNullOrEmpty(alias), "!String.IsNullOrEmpty(alias)");
    map.Add(alias, algorithm);
    }
                if (!map.ContainsKey(algorithm.Name))
    {
    map.Add(algorithm.Name, algorithm);
    }
                if (!map.ContainsKey(algorithm.FullName))
    {
    map.Add(algorithm.FullName, algorithm);
    }
    }
            /// <summary>
    ///     <para>
    ///         CreateFactoryFromName is similar to <see cref="CreateFromName"/>, except that instead of
    ///         returning a single instance of a crypto algorithm, CreateFactoryFromName returns a
    ///         function that can create new instances of the algorithm.   This function will be more
    ///         efficient to use if multiple intsances of the same algorithm are needed than calling
    ///         CreateFromName repeatedly.
    ///     </para>
    ///     <para>
    ///         Name comparisons are case insensitive.
    ///     </para>
    ///     <para>
    ///         This method is thread safe.
    ///     </para>
    /// </summary>
    /// <param name="name">name of the algorithm to create a factory for</param>
    /// <exception cref="ArgumentNullException">if <paramref name="name"/> is null</exception>
    public static Func<object> CreateFactoryFromName(string name)
    {
    // Figure out what type of algorithm we need to create
    object algorithm = CreateFromName(name);
    if (algorithm == null)
    {
    return null;
    }
                Type algorithmType = algorithm.GetType();
                // Since we only need the algorithm type, rather than the full algorithm itself, we can clean up
    // the algorithm instance if it is disposable
    IDisposable disposableAlgorithm = algorithm as IDisposable;
    if (disposableAlgorithm != null)
    {
    disposableAlgorithm.Dispose();
    }
                // Create a factory delegate which returns new instances of the algorithm type
    NewExpression algorithmCreationExpression = Expression.New(algorithmType);
    LambdaExpression creationFunction = Expression.Lambda<Func<object>>(algorithmCreationExpression);
    return creationFunction.Compile() as Func<object>;
    }
            /// <summary>
    ///     <para>
    ///         CreateFromName attempts to map the given algorithm name into an instance of the specified
    ///         algorithm. It works with both the built in algorithms in the .NET Framework 3.5 as well
    ///         as the algorithms in the Security.Cryptography.dll assembly. Since it does work with the
    ///         built in crypto types, CryptoConfig2.CreateFromName can be used as a drop-in replacement
    ///         for <see cref="CryptoConfig.CreateFromName(string)" />
    ///     </para>
    ///     <para>
    ///         Types in System.Core.dll and Security.Cryptography.dll can be mapped either by their
    ///         simple type name or their namespace type name. For example, AesCng and
    ///         Security.Cryptography.AesCng will both create an instance of the <see cref="AesCng" />
    ///         type. Additionally, the following names are also given mappings in CryptoConfig2:
    ///     </para>
    ///     <list type="bullet">
    ///         <item>AES - <see cref="AesCryptoServiceProvider" /></item>
    ///         <item>ECDsa - <see cref="ECDsaCng" /></item>
    ///         <item>ECDH - <see cref="ECDiffieHellmanCng" /></item>
    ///         <item>ECDiffieHellman - <see cref="ECDiffieHellmanCng" /></item>
    ///     </list>
    ///     <para>
    ///         Name comparisons are case insensitive.
    ///     </para>
    ///     <para>
    ///         This method is thread safe.
    ///     </para>
    /// </summary>
    /// <param name="name">name of the algorithm to create</param>
    /// <exception cref="ArgumentNullException">if <paramref name="name"/> is null</exception>
    public static object CreateFromName(string name)
    {
    if (name == null)
    throw new ArgumentNullException("name");
                // First try to use standard CryptoConfig to create the algorithm
    object cryptoConfigAlgorithm = CryptoConfig.CreateFromName(name);
    if (cryptoConfigAlgorithm != null)
    {
    return cryptoConfigAlgorithm;
    }
                // If we couldn't find the algorithm in crypto config, see if we have an internal mapping for
    // the name
    s_algorithmMapLock.EnterReadLock();
    try
    {
    Type cryptoConfig2Type = null;
    if (s_algorithmMap.TryGetValue(name, out cryptoConfig2Type))
    {
    return Activator.CreateInstance(cryptoConfig2Type);
    }
    }
    finally
    {
    s_algorithmMapLock.ExitReadLock();
    }
                // Otherwise we don't know how to create this type, so just return null
    return null;
    }
    }
    }
    Here is the test code to read in PKDF2 from the current application domain.
    using System;
    using System.Text;
    using System.Security.Cryptography;
    using FDVS.Web.Portal.Membership.ClassLib;
    namespace TestPBKDF2
    {
    class Program
    {
    private static string EncodePassword(string pass, string salt)
    {
    byte[] bIn = Encoding.Unicode.GetBytes(pass);
    byte[] bSalt = Convert.FromBase64String(salt);
    byte[] bRet = null;
    // user defined PBKDF2 custom algorithm
    // WE ARE NOT USING MACHINE.CONFIG FOR NOW. WE ARE USING OUR OWN MyCryptoConfig TO ADD THE NEW ALGO TO THE CURRENT APP DOMAIN.
    MyCryptoConfig.AddAlgorithm(typeof(HashPbkdf2),
    "PKDF2",
    "HashPbkdf2",
    "ClassLib");
                HashAlgorithm hm = MyCryptoConfig.CreateFromName("PKDF2") as HashAlgorithm;
                if (hm == null)
    {
    return "HashAlgorithm cannot be NULL. Exception";
    }
                if (hm is KeyedHashAlgorithm)
    {
    KeyedHashAlgorithm kha = (KeyedHashAlgorithm)hm;
    if (kha.Key.Length == bSalt.Length)
    {
    kha.Key = bSalt;
    }
    else if (kha.Key.Length < bSalt.Length)
    {
    byte[] bKey = new byte[kha.Key.Length];
    Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length);
    kha.Key = bKey;
    }
    else
    {
    byte[] bKey = new byte[kha.Key.Length];
    for (int iter = 0; iter < bKey.Length;)
    {
    int len = Math.Min(bSalt.Length, bKey.Length - iter);
    Buffer.BlockCopy(bSalt, 0, bKey, iter, len);
    iter += len;
    }
    kha.Key = bKey;
    }
    bRet = kha.ComputeHash(bIn);
    }
    else
    {
    byte[] bAll = new byte[bSalt.Length + bIn.Length];
    Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
    Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
    bRet = hm.ComputeHash(bAll);
    }
                return Convert.ToBase64String(bRet);
    }
            private static string CreateSalt()
    {
    int max_length = 32;
    byte[] userBytes = new byte[max_length];
    string salt;
    long XORED = 0x00;
                foreach (int x in userBytes)
    XORED = XORED ^ x;
                Random rand = new Random(Convert.ToInt32(XORED));
    salt = rand.Next().ToString();
    salt += rand.Next().ToString();
    salt += rand.Next().ToString();
    salt += rand.Next().ToString();
    return salt;
    }
            static void Main(string[] args)
    {
    string pass = "Password@123";
    string salt = CreateSalt();
    // Call EncodePassword
    string encodedPassword = EncodePassword(pass, salt);
    Console.WriteLine(encodedPassword);
    }
    }
    }

    AF: AppFabric Invalid Version Error

    $
    0
    0

    On a clustered environment when you try to start the AppFabric caching service you may encounter the error AppFabric Invalid Version Error.

    You will see the following exception logged in the AppFabric event logs

    AppFabric Caching service crashed with exception {Microsoft.ApplicationServer.Caching.DataCacheException: ErrorCode<ERRService0001>:SubStatus<ES0001>:Service initialization failed. No user action required. ---> Microsoft.Fabric.Common.OperationCompletedException: Operation completed with an exception ---> Microsoft.Fabric.Federation.InvalidVersionException: Node with version: Microsoft.ApplicationServer.Caching.ServerVersionInfo received an abort because of invalid version

    You can use the Get-CacheHost command to view the version of the Caching Service on each cache host

    The following is the output of the Get-CacheHost command executed on a clustered environment.

    HostName : CachePort Service Name Service Status Version Info
    CACHESERVER1:22233 AppFabricCachingService UP 3 [3,3][1,3]
    CACHESERVER2:22233 AppFabricCachingService UP 3 [3,3][1,3]
    CACHESERVER3:22233 AppFabricCachingService DOWN 3 [1,3][1,1]
    CACHESERVER4:22233 AppFabricCachingService DOWN 3 [1,3][1,1]

     

    The Version Info column in this example contains the value 3 [3,3][1,3]. This corresponds to the following version details:

     3 The version of the Caching Service running on that cache host.
    [3,3] The range of caching service versions that can run on that particular host within the cache cluster during the online upgrade.
    [1,3] The range of cache client versions that can use the cache cluster.

     

    The hosts are running the updated Caching Service with a version of 3. But if we look at the allowed server and client versions there is a mismatch on the server instance CACHESERVER3 and CACHESERVER4.

    Use Update-CacheHostAllowedVersions to update the allowed server and client server versions

    Below is the command to update the server and client version range.

    Update-CacheHostAllowedVersions -BeginServerVersion 3

    Update-CacheHostAllowedVersions -EndClientVersion 3

     

    After updating the versions, the caching service started without any issues. The following is the output of the Get-CacheHost command.

     

    HostName : CachePort Service Name Service Status Version Info
    CACHESERVER1:22233 AppFabricCachingService UP 3 [3,3][1,3]
    CACHESERVER2:22233 AppFabricCachingService UP 3 [3,3][1,3]
    CACHESERVER3:22233 AppFabricCachingService UP 3 [3,3][1,3]
    CACHESERVER4:22233 AppFabricCachingService UP 3 [3,3][1,3]

     

    Reference Articles

    https://msdn.microsoft.com/en-us/library/hh343304(v=azure.10).aspx

     

    Created by

    Sakthivel Ramasamy


    Recovering COM+ Applications from a hang state

    $
    0
    0

    Recently, I worked with some customer problems where they encountered issues with COM+ application going into a hang state. This causes sometimes the main applications to wait indefinitely without recovering from the problem.

    In such circumstances, many users/developers of the com+ application manually check the state of the com+ application in the component services page and then they will kill the com+ surrogate process to recover from the situation.

    However, this manual recovery method is a tedious process and it cannot be easily handled when there are multiple com+ application in some large-scale enterprises.

    Solution/Workaround

    To automatically recover from these situations, you can configure the system to perform the following actions when a long-running COM+ component is detected. The problem can be solved in one of the two following ways, depending on the requirements of your application and server:

    1. To Change Global COM+ behavior

    To do this, use the following registry values:

    In the registry path: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\COM3\AutoDump (Please create the keys if it does not exist), add the following keys and their corresponding values:

    Key Value name: AverageCallThreshold

    Data type: REG_DWORD

    Value: Set to 3000 - this is maximum allowed duration of process execution, in seconds. If it is exceeded, a warning is raised.

    Key Value name: Terminate

    Data type: REG_DWORD

    Value: Set to 1 - Process will be terminated on exceeding the maximum call threshold value which is configured above.

    Key Value name : DumpType

    Data type : REG_DWORD

    Value: set to 2- No dump file will be generated. You can set the value as 0 if you want to generate a full dump file or set it to 1 for minidump file.

    2. To Modify behavior of a specific COM+ application

    In order to change the behavior of a specific COM+ application, instead of making a global server-wide change, create the above keys (AverageCallThreshold, Terminate and DumpType) and values in the following registry path:

    HKEY_CLASSES_ROOT\AppId\{<YourAppID>}\AutoDump\{<YourCLSID>}.

     

    Reference Articles

    https://support.microsoft.com/en-in/kb/910904

    Created By

    Sakthivel Ramasamy

     

    WIF: Active authentication against “usernamemixed” ADFS endpoint

    $
    0
    0

    Scenario
    One of my customers recently wanted to help write code/ configuration in the following scenario.

    ASP.NET web application and WCF service would be hosted on two different machines (IIS web server). User would access web application. And, it would make an "active authentication" call to "usernamemixed" ADFS endpoint. Once it has the token from ADFS, it would be allowed to make the backend WCF service (without any prompt for username/ password) call.

     

    Solution

    For solution to the above challenge, the following are the steps to be followed (one-by-one).

     

    1. Launch VS 2015 as an administrator
    2. Create a new project with "WCF service application" template
    3. Let it be named "ActiveScenarioService2015"
    4. Modify "Iservice1.cs" to contain the following methods:

     

    		using System.ServiceModel;
    
    		namespace ActiveScenarioService2015
    		{
    			[ServiceContract]
    			public interface IService1
    			{
    				[OperationContract]
    				string GetData(int value);
    
    				[OperationContract]
    				string GetClaim();
    			}
    		}
    
    
    1. Modify "Service1.svc.cs" to contain the following code:

     

    		using System.Security.Claims;
    		using System.ServiceModel;
    		using System.Text;
    
    		namespace ActiveScenarioService2015
    		{
    			public class Service1 : IService1
    			{
    				public string GetData(int value)
    				{
    					return string.Format("You entered: {0}", value);
    				}
    
    				public string GetClaim()
    				{
    					ClaimsPrincipal claimsPrincipal = OperationContext.Current.ClaimsPrincipal;
    
    					StringBuilder builder = new StringBuilder();
    
    					builder.AppendLine(string.Format("User identity: {0}", claimsPrincipal.Identity.Name));
    					foreach (var claim in claimsPrincipal.Claims)
    					{
    						builder.AppendLine(string.Format("Claim Type: {0} Claim Value: {1}", claim.Type, claim.Value));
    					}
    
    					return builder.ToString();
    				}
    			}
    		}
    
    
    1. Ensure you can build the project fine
    2. Add reference to assemblies such as "System.IdentityModel" and "System.IdentityModel.Services"
    3. Manage Nuget packages -> find "System.IdentityModel.Tokens.ValidatingIssuerNameRegistry" -> "Install"
    4. Modify the "web.config" to contain the following configuration items:
    	<configuration>
    	  <configSections>
    		<section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
    	  </configSections>
    	  <appSettings>
    		<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
    		<add key="ida:FederationMetadataLocation" value="https://adfs.contoso.com/federationmetadata/2007-06/federationmetadata.xml" />
    		<add key="ida:ProviderSelection" value="productionSTS" />
    	  </appSettings>
    	  <system.web>
    		<compilation debug="true" targetFramework="4.6.1" />
    		<httpRuntime targetFramework="4.6.1"/>
    	  </system.web>
    	  <system.serviceModel>
    		<behaviors>
    		  <serviceBehaviors>
    			<behavior>
    			  <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
    			  <serviceDebug includeExceptionDetailInFaults="true"/>
    			  <serviceCredentials useIdentityConfiguration="true">
    				<serviceCertificate findValue="CN=localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName"/>
    			  </serviceCredentials>
    			</behavior>
    		  </serviceBehaviors>
    		</behaviors>
    		<protocolMapping>
    			<add binding="ws2007FederationHttpBinding" scheme="https" />
    		</protocolMapping>
    		<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    		<bindings>
    		  <ws2007FederationHttpBinding>
    			<binding>
    			  <security mode="TransportWithMessageCredential">
    				<message>
    				  <issuerMetadata address="https://adfs.contoso.com/adfs/services/trust/mex" />
    				</message>
    			  </security>
    			</binding>
    		  </ws2007FederationHttpBinding>
    		</bindings>
    	  </system.serviceModel>
    	  <system.webServer>
    		<modules runAllManagedModulesForAllRequests="true"/>
    		
    		<directoryBrowse enabled="true"/>
    	  </system.webServer>
    	  <system.identityModel>
    		  <identityConfiguration>
    			<audienceUris>
    			  <add value="https://win7.contoso.com/ActiveScenarioService2015/Service1.svc" />
    			</audienceUris>
    			<issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
    			  <authority name="http://adfs.contoso.com/adfs/services/trust">
    				<keys>
    				  <add thumbprint="BDF5186BFF80A2BC188508F4EEE0E5737CBCD30E" />
    				</keys>
    				<validIssuers>
    				  <add name="http://adfs.contoso.com/adfs/services/trust" />
    				</validIssuers>
    			  </authority>
    			</issuerNameRegistry>
    			
    			<certificateValidation certificateValidationMode="None" />
    		</identityConfiguration>
    	  </system.identityModel>
    	</configuration>
    
    1. Please note about two applications over here
      1. "CN=localhost"
        • you can launch your certificate store (run -> mmc -> add/ remove snap in/ certificates/ Add .. Select "Computer Account"/ OK
        • you would see a certificate named "localhost" in your "personal" store
        • if not, create a self-signed certificate with "localhost" name form IIS manager
      1. "BDF5186BFF80A2BC188508F4EEE0E5737CBCD30E"
        • it would be the thumbprint value of your "ADFS signing" certificate

     

     

    1. Host the WCF service on IIS
    2. One simple method is just to create virtual directory via VS project properties

    1. Go to IIS Manager/ Create an application pool for "v4.0" .NET framework named as "ActiveScenarioService2015" (for example)
    2. Ensure "Load User Profile" set to "true" for the application pool
    3. Map the IIS hosted application "ActiveScenarioService2015" to the newly created application pool "ActiveScenarioService2015"
    4. Ensure you can browse your service application fine (for example - https://win7.contoso.com/ActiveScenarioService2015/Service1.svc)

     

    1. Next step is to provision the service application on ADFS (in this case - it was 3.0)
    2. On the IIS server "win7.conrtoso.com", Right click the "localhost" certificate in store, "All Tasks" -> "Export" .. Without private key -> to a DER encoded binary X.509 certificate (.CER) file
    3. Go to your ADFS server, and bring in the ".CER" for being used as "encryption" certificate
    4. Ensure ADFS "usernamemixed" endpoint is enabled
    5. Add "Relying Party Trust"

     

     

     

     

    Browse the exported "localhost" certificate (.cer) on ADFS server

     

    Don't select any - because we are using ADFS "usernamemixed" endpoint for user authentication

     

    Provide "Relying Party" identifier to be the service URL

     

     

    Finish

     

    Add Rule

     

    OK

     

     

    1. Now, "RP Trust" provisioning is over in ADFS

     

    1. Next, it is turn of the client side to be ready

     

    1. Create an MVC web application project named "ActiveScenarioWebClient" in VS 2015
    2. Ensure you can build the web application project fine
    3. "Add Service Reference" to the WCF service URL https://win7.contoso.com/ActiveScenarioService2015/Service1.svc
    4. Build the project
    5. Now, turn to "web.confg" and ensure following sections appended or appeared in <system.serviceModel .. /> section

     

      <system.serviceModel>
        <bindings>
          <basicHttpBinding>
            <binding name="BasicHttpBinding_IService1" />
          </basicHttpBinding>
          <wsHttpBinding>
            <binding name="https://adfs.contoso.com/adfs/services/trust/2005/certificatemixed">
              <security mode="TransportWithMessageCredential">
                <transport clientCredentialType="None" />
                <message clientCredentialType="Certificate" establishSecurityContext="false" />
              </security>
            </binding>
            <binding name="https://adfs.contoso.com/adfs/services/trust/2005/usernamemixed">
              <security mode="TransportWithMessageCredential">
                <transport clientCredentialType="None" />
                <message clientCredentialType="UserName" establishSecurityContext="false" />
              </security>
            </binding>
            <binding name="https://adfs.contoso.com/adfs/services/trust/13/windowstransport">
              <security mode="Transport" />
            </binding>
          </wsHttpBinding>
          <ws2007HttpBinding>
            <binding name="https://adfs.contoso.com/adfs/services/trust/13/certificatemixed">
              <security mode="TransportWithMessageCredential">
                <transport clientCredentialType="None" />
                <message clientCredentialType="Certificate" establishSecurityContext="false" />
              </security>
            </binding>
            <binding name="https://adfs.contoso.com/adfs/services/trust/13/usernamemixed">
              <security mode="TransportWithMessageCredential">
                <transport clientCredentialType="None" />
                <message clientCredentialType="UserName" establishSecurityContext="false" />
              </security>
            </binding>
          </ws2007HttpBinding>
          <ws2007FederationHttpBinding>
            <binding name="WS2007FederationHttpBinding_IService1_username">
              <security mode="TransportWithMessageCredential">
                <message>
                  <issuer address="https://adfs.contoso.com/adfs/services/trust/2005/usernamemixed"
                    binding="wsHttpBinding" bindingConfiguration="https://adfs.contoso.com/adfs/services/trust/2005/usernamemixed">
                  </issuer>
                  <issuerMetadata address="https://adfs.contoso.com/adfs/services/trust/mex" />
                  <tokenRequestParameters>
                    <trust:SecondaryParameters xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">
                      <trust:KeyType xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey</trust:KeyType>
                      <trust:KeySize xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">256</trust:KeySize>
                      <trust:KeyWrapAlgorithm xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p</trust:KeyWrapAlgorithm>
                      <trust:EncryptWith xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/04/xmlenc#aes256-cbc</trust:EncryptWith>
                      <trust:SignWith xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2000/09/xmldsig#hmac-sha1</trust:SignWith>
                      <trust:CanonicalizationAlgorithm xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512">http://www.w3.org/2001/10/xml-exc-c14n#http://www.w3.org/2001/04/xmlenc#aes256-cbc</trust:EncryptionAlgorithm>
                    </trust:SecondaryParameters>
                  </tokenRequestParameters>
                </message>
              </security>
            </binding>
          </ws2007FederationHttpBinding>
        </bindings>
        <client>
          <endpoint address="http://win7.contoso.com/ActiveScenarioService2015/Service1.svc"
            binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
            contract="ServiceReference1.IService1" name="BasicHttpBinding_IService1" />
          <endpoint address="https://win7.contoso.com/ActiveScenarioService2015/Service1.svc"
            binding="customBinding" bindingConfiguration="WS2007FederationHttpBinding_IService1"
            contract="ServiceReference1.IService1" name="WS2007FederationHttpBinding_IService1" />
          <endpoint address="https://win7.contoso.com/ActiveScenarioService2015/Service1.svc"
            binding="ws2007FederationHttpBinding" bindingConfiguration="WS2007FederationHttpBinding_IService1_username"
            contract="ServiceReference1.IService1" name="WS2007FederationHttpBinding_IService1_username" />
        </client>
      </system.serviceModel>
    
    

     

     

      1. Create a controller named "TestController"
    	using ActiveScenarioWebClient.ServiceReference1;
    	using System;
    	using System.Collections.Generic;
    	using System.Linq;
    	using System.Web;
    	using System.Web.Mvc;
    
    	namespace ActiveScenarioWebClient.Controllers
    	{
    		public class TestController : Controller
    		{
    			// GET: Test
    			public ActionResult Index()
    			{
    				var proxy = new Service1Client("WS2007FederationHttpBinding_IService1_username");
    				proxy.ClientCredentials.UserName.UserName = @"contoso\administrator";
    				proxy.ClientCredentials.UserName.Password = "Password01!";
    				var response = proxy.GetClaim();
    				ViewBag.Response = response;
    				proxy.Close();
    
    				return View();
    			}
    		}
    	}
    
      1. Add view "Index.cshtml"
        @{
              ViewBag.Title = "Index";
              }
     
              <h2>Index</h2>
              <p>@ViewBag.Response</p>
    

     

    1. Host the web application on IIS
    2. Access your web application over browser (e.g. https://aadconnect.contoso.com/ActiveScenarioWebClient/test)
    3. The output would be something like

    1. Once we have this much output, we are good with the authentication flow

     

    Note:

    The sample application tried above is up for your reference is available here. Please launch the VS solution as an administrator to view the sample application.

     

     

    I hope this helps!

     

    WS/WCF: Remove Server Header

    $
    0
    0

    Requirement:
    Need to suppress all instances of the HTTP ‘Server’ header from all HTTP responses including invalid requests that never even reach the application process.

    Why we need this:
    Exposing Server headers as part of response payload is security vulnerability documented under https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html section 14.38.

    Workaround for Self Host WCF Services:
    Set below registry flag to: 2
    HKLM\SYSTEM\CurrentControlSet\Services\HTTP\Parameters\DisableServerHeader

    Setting this to 2 will ensure that self host WCF services no longer sends the SERVER header and thus ensure we are security compliant.
    Please note that this disables ALL server headers.

    The default value of 0 enables the header, and the value of 1 disables server header from DRIVER (http.sys), but app can still have headers.

    Workaround for IIS hosted applications
    1. Stop the World Wide Web Publishing Service (if IIS services are not required on the server).
    2. If they are required, then we would need to use the IIS URL Rewrite module and delete the server header itself.
    Refer: https://blogs.msdn.microsoft.com/varunm/2013/04/23/remove-unwanted-http-response-headers/

    I hope this helps!

    Thanks
    Saurabh Somani

    WIF: Fetch SAML tokens from IssuedToken* endpoint for backend service call

    $
    0
    0

    Recently, I have across a scenario where the requirement is to fetch token from "IssuedToken*" active ADFS endpoints. Once application has the token from "IssuedToken*" endpoint, it would have to present the token to backend WCF service application operation invokes.

    We went through a lot of hardship to figure out the right binding and ADFS configuration in order to find right solution. Since there is not much public documentation around it, it was a tough goal to achieve.

    So, I am just sharing the steps and sample for everyone's benefit.

     

    The whole next set of steps are written with the assumption that application and RP Trust endpoints would be part of same domain. Towards the end, I would clarify for the two domain scenario.

     

    Steps for WCF Service Application

    1. Launch VS 2012 as an administrator, and create a "WCF Service Application".
    2. Make your service ready with business logic.
    3. Right click on the project/ go to Properties/ go to Web tab/ Create virtual directory/ Deploy to IIS.
    4. Ensure the WCF service is accessible over https.
    5. Right click on the project, and go to "Identity and Access tool".
    6. Point to ADFS metadata URL and APP ID URL.

    1. Let's go to "web.config"
    2. Ensure to have the following configuration under System.IdentityModel.
    <system.identityModel>
       <identityConfiguration saveBootstrapContext="true">
       <audienceUris>
           <add value="https://aadconnect.contoso.com/Issuedtokenmixedasymmetricbasic256WcfService1/Service1.svc" />
           <add value="https://test1.com/"/>
       </audienceUris>
       <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
           <authority name="http://adfs.contoso.com/adfs/services/trust">
               <keys>
                   <add thumbprint="567845870C8A0D9BA17503B3B14EC604B95F41AF" />
               </keys>
               <validIssuers>
                    <add name="http://adfs.contoso.com/adfs/services/trust" />
               </validIssuers>
           </authority>
       </issuerNameRegistry>
       <!--certificationValidationMode set to "None" by the the Identity and Access Tool for Visual Studio. For development purposes.-->
       <certificateValidation certificateValidationMode="None" />
    </identityConfiguration>
    </system.identityModel>
    

     

    1. Update the System.ServiceModel section with "ws2007FederationHttpBinding" for the WCF service endpoint.
    <system.serviceModel>
        <behaviors>
            <serviceBehaviors>
                <behavior>
                    <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
                    <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
                    <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
                    <serviceDebug includeExceptionDetailInFaults="false" />
                    <serviceCredentials useIdentityConfiguration="true">
                         <!--Certificate added by Identity and Access Tool for Visual Studio.-->
                         <serviceCertificate findValue="CN=localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectDistinguishedName" />
                    </serviceCredentials>
                </behavior>
            </serviceBehaviors>
         </behaviors>
         <protocolMapping>
              <add scheme="https" binding="ws2007FederationHttpBinding" />
         </protocolMapping>
         <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
         <bindings>
         <ws2007FederationHttpBinding>
           <binding>
             <security mode="TransportWithMessageCredential">
               <message issuedKeyType="BearerKey">
                 <issuerMetadata address="https://adfs.contoso.com/adfs/services/trust/mex" />
               </message>
             </security>
           </binding>
         </ws2007FederationHttpBinding>
         </bindings>
    </system.serviceModel>
    

     

    1. Ensure WCF service URL is accessible over browser, and https.

     

    Steps for ADFS Provisioning

    1)

    Set up RP Trust with a custom identifier to map the ADFS endpoint '/adfs/services/trust/13/usernamemixed'.

    Since it is an active authentication call, there is no need to set up for passive endpoint.

    Ensure you use your "ADFS encryption certificate" to encrypt the payload.

    Set up the claim rule to fetch info from "Active Directory".

     

    2)

    Set up RP Trust with a custom identifier to map the ADFS endpoint '/adfs/services/trust/13/issuedToken*******'.

    Since it is an active authentication call, there is no need to set up for passive endpoint.

    Set up the claim rule as the following:

    3)

    Set up RP Trust for the WCF service endpoint address.

    Since it is an active authentication call, there is no need to set up for passive endpoint.

     

    Set the "Encryption" certificate, to help encrypt the payload from the RP Trust endpoint. The certificate used in this case would be the same "localhost" certificate used on the web server, where WCF service application was hosted. I used a self-signed certificate in my case.

    Set up the claim rule like the following for the ADFS RP Trust endpoint.

     

    Steps for Active client application

    Broadly, we have to perform the following steps (in sequence) on the client application:

    1. Using "UserNameWSTrustBinding", client gets token from "usernamemixed" ADFS endpoint.
    2. Using "IssuedTokenWSTrustBinding", client presents the above token to "issuedtokenmixedsymmetricbasic256" ADFS endpoint, and gets a token in response.
    3. Using "WS2007FederationHttpBinding", the token is presented during the WCF service operation call.

     

    So, on the application side, we need to write the following code.

    1. Create a console application.
    2. "Add service reference" to your WCF service endpoint.
    3. Update your "App.config" with the following appSettings.

     

    <appSettings>
        <add key="usernamemixedEP" value="https://adfs.contoso.com/adfs/services/trust/13/usernamemixed"/>
        <add key="usernamemixedAppliesTo" value="https://Test.com/"/>
        <!-- You can enable any of the following issuedToken based ADFS endpoints. -->
        <!-- https://adfs.contoso.com/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256 -->
        <!-- https://adfs.contoso.com/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256sha256 -->
        <!-- https://adfs.contoso.com/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256 -->
        <!-- https://adfs.contoso.com/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256sha256 -->
        <add key="issuedtokenEP" value="https://adfs.contoso.com/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256"/> 
        <add key="issuedtokenAppliesTo" value="https://test1.com/"/>
        <!-- Set false if SAML 2.0 Assertion expected. Otherwise, let it be true. -->
        <add key="saml10TokenType" value="false"/>
        <add key="username" value="JohnAdmin@contoso.com"/>
        <add key="password" value="Password01!"/>
        <add key="serviceEpAddress" value="https://aadconnect.contoso.com/Issuedtokenmixedasymmetricbasic256WcfService1/Service1.svc"/>
    </appSettings>
    

     

    1. The program should be updated with the following source code.

     

    		    class Program
    		    {
    		        static void Main(string[] args)
    		        {
    		            var tokenType = ConfigurationManager.AppSettings["saml10TokenType"].ToString();
    		            bool isSAML10Token;
    		            Boolean.TryParse(tokenType, out isSAML10Token);
    		            if (isSAML10Token)
    		            {
    		                tokenType = "urn:oasis:names:tc:SAML:1.0:assertion";
    		            }
    		            else
    		            {
    		                tokenType = "urn:oasis:names:tc:SAML:2.0:assertion";
    		            }
    		
    		            var usernameToken = GetIdentityProviderToken(tokenType);
    		
    		            var issuedToken = GetRSTSToken(usernameToken, tokenType);
    		
    		            var serviceResponse = CallService_IssuedToken(issuedToken);
    		
    		            Console.WriteLine(serviceResponse);
    		            Console.ReadLine();
                            }
    
    		        private static SecurityToken GetIdentityProviderToken(string tokenType)
    		        {
    		            var binding = new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential);
    		
    		            var factory = new WSTrustChannelFactory(binding, ConfigurationManager.AppSettings["usernamemixedEP"].ToString())
    		            {
    		                TrustVersion = TrustVersion.WSTrust13
    		            };
    		
    		            factory.Credentials.UserName.Password = ConfigurationManager.AppSettings["password"].ToString();
    		            factory.Credentials.UserName.UserName = ConfigurationManager.AppSettings["username"].ToString();
    		            factory.Credentials.SupportInteractive = false;
    		            factory.Credentials.UseIdentityConfiguration = true;
    		
    		            var rst = new RequestSecurityToken
    		            {
    		                RequestType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue",
    		                AppliesTo = new EndpointReference(ConfigurationManager.AppSettings["usernamemixedAppliesTo"].ToString()),
    		                KeyType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey",
    		                TokenType = tokenType
    		            };
    		
    		            var channel = factory.CreateChannel();
    		            return channel.Issue(rst);
    		        }
    
          		        private static SecurityToken GetRSTSToken(SecurityToken token, string tokenType)
    		        {
    		            var binding = new IssuedTokenWSTrustBinding();
    		            binding.SecurityMode = SecurityMode.TransportWithMessageCredential;
    		
    		            var issuredTokenEP = ConfigurationManager.AppSettings["issuedtokenEP"].ToString();
    		            if (issuredTokenEP.ToLower().EndsWith("issuedtokenmixedasymmetricbasic256sha256")
    		                || issuredTokenEP.ToLower().EndsWith("issuedtokenmixedsymmetricbasic256sha256"))
    		            {
    		                binding.AlgorithmSuite = SecurityAlgorithmSuite.Basic256Sha256;
    		            }
    		
    		            var factory = new WSTrustChannelFactory(binding, issuredTokenEP);
    		            factory.TrustVersion = TrustVersion.WSTrust13;
    		            factory.Credentials.SupportInteractive = false;
    		            factory.Credentials.UseIdentityConfiguration = true;
    		            
    		            var rst = new RequestSecurityToken
    		            {
    		                RequestType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue",
    		                AppliesTo = new EndpointReference(ConfigurationManager.AppSettings["issuedtokenAppliesTo"].ToString()),
    		                KeyType = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer",
    		                TokenType = tokenType,
    		            };
    		
    		            var channel = factory.CreateChannelWithIssuedToken(token);
    		
    		            return channel.Issue(rst);
    		        }
      
    		        private static string CallService_IssuedToken(SecurityToken token)
    		        {
    		            // Creating the channel and calling it
    		            var binding = new WS2007FederationHttpBinding(WSFederationHttpSecurityMode.TransportWithMessageCredential);
    		            binding.Security.Message.IssuedKeyType = SecurityKeyType.BearerKey;
    		
    		            var serviceEpAddress = ConfigurationManager.AppSettings["serviceEpAddress"].ToString();
    		            var factory = new ChannelFactory(binding, new EndpointAddress(serviceEpAddress));
    		            
    		            // Create a channel
    		            IService1 client = factory.CreateChannelWithIssuedToken(token);
    		            
    		            // Invoke the service operation
    		            var response = client.GetData(12);
    		            ((IClientChannel)client).Close();
    		
    		            return response;
    		        }		
                         }
    
    

     

    1. The bindings such as UserNameWSTrustBinding and IssuedTokenWSTrustBinding are part of WIF V1, i.e. Microsoft.IdentityModel. So, the trick is to port the old code in your application explicitly.
    2. To keep safe of length of this document, I am not putting the whole code. I would attach the whole source code on OneDrive for reference.

     

    Note
    It is just configurable. Change the "appSettings" for appropriate keys to make the code functional.

     

    Two-domain ADFS configuration
    Before you create the claims provider trust, you’ll need the following from the IDP:
    - Federation service identifier.  This is also known as the Issuer or entity-Id
    - Public key of the token signing certificate

    Next,
    1. In the ADFS management console, right click on Claims Provider Trusts then click Add Claims Provider Trust
    2. At the Welcome page, click Start
    3. At the Select Data Source page, select Enter claims provider trust data manually
    4. Click Next
    5. At the Specify Display Name page, in the Display name enter a name
    6. Click Next
    7. At the Configure URL page, in the WS-Federation Passive URL enter any URL as long as it’s https.
    8. Click Next
    9. At the Configure Identifier page, in the Claims provider trust identifier field enter the Identifier (aka entity-ID) of the identity provider (aka claims provider)
    10. Click Next
    11. At the Configure Certificates page, click Add
    12. Select the token signing certificate from the identity provider
    13. Click Next
    14. At the Ready to Add Trust page, click Next
    15. Click Close
    16. Set your claim rules.  This can be done now or later

     

    The full sample application is available on OneDrive, and also on GitHub (welcome to clone it and play around).

     

    I hope this helps!

    WCF: Windows authentication and streaming support

    $
    0
    0

    Issue:
    We had a requirement to use STREAMING protocol along with WINDOWS AUTHENTICATION.

    Out of the box configuration:

     

     

     

     

     

     

     

     

    Above binding configuration will not help us here and we will end up seeing below error.

    Error Message:
    $exception {"HTTP request streaming cannot be used in conjunction with HTTP authentication. Either disable request streaming or specify anonymous HTTP authentication.\r\nParameter name: bindingElement"} System.ArgumentException
    at System.ServiceModel.Channels.HttpChannelFactory`1..ctor(HttpTransportBindingElement bindingElement, BindingContext context)

    ANALYSIS
    As per HttpChannelFactory ctor https://referencesource.microsoft.com/#System.ServiceModel/System/ServiceModel/Channels/HttpChannelFactory.cs,9739a9339ab039dc it is not allowed.

    if (TransferModeHelper.IsRequestStreamed(bindingElement.TransferMode) && bindingElement.AuthenticationScheme!= AuthenticationSchemes.Anonymous)
    {
    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("bindingElement", SR.GetString(SR.HttpAuthDoesNotSupportRequestStreaming));
    }

    Response from WCF PG:
    The problem is that a stream can't be replayed without buffering.
    1. When using authentication, the first request to the server has no authentication and the server will respond with an authentication challenge.
    2. WCF uses the Expect 100-Continue header to avoid the request body being sent in the non-authenticated first request.
    3. The client then needs to send the request a second time.
    4. If there is a very high latency to the server, Expect-100 continue mechanism effectively is disabled. Also, the server might not support the Expect 100-Continue capability.
    5. If we need to resend the body, this works fine if the request is buffered.
    6. But, we always have a chance if the body needs to be re-sent when using authentication.
    This becomes fundamentally incompatible with streamed mode as it is unpredictable whether any one request will succeed.

    As for a potential workaround (where performance is a constraint), there are several possibilities, but the simplest is probably to use WebSockets.
    With WebSockets, the authentication is done on the HTTP WebSocket upgrade handshake. So, there’s no problem with the payload being part of the authentication conversation. WebSockets can handle streamed mode without any issue.

    The binding will be similar to this:

     

     

     

     

     

     

     

     

    Hope this information helps!

    Content created by:
    Nadim Akhtar

    Content published by:
    Saurabh Somani

    Viewing all 164 articles
    Browse latest View live


    <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>