I have finished updating the code for using WCF since it is in the current version of dev I was working on. Then I used code that was posted on Amazon forums, but made it a little easier.
UPDATE: a new, easier to use code that allows you to use configuration settings for everything
In the previous code I posted, and what I saw elsewhere when the service object is created, one of the constructor overrides is used to tell it to use HTTPS, give it an HTTPS URL and manually attach a message inspector to do the signing. The error when using the default constructor is that you lose the ability to configure the service through the configuration file.
Since then, I redid this code so that you can continue to use the default constructor, without parameters, and configure the service through the configuration file. The good thing about this is that you donβt need to recompile your code to use it, or make changes after deployment, such as maxStringContentLength (which caused this change to happen, as well as detect problems associated with doing all this in the code), I also updated the signature part a bit so that you can specify which hash algorithm to use, as well as the regular expression to retrieve the Action.
These two changes are due to the fact that not all Amazon web services use the same hashing algorithm, and Action may need to be extracted differently. This means that you can reuse the same code for each type of service by simply changing the value in the configuration file.
public class SigningExtension : BehaviorExtensionElement { public override Type BehaviorType { get { return typeof(SigningBehavior); } } [ConfigurationProperty("actionPattern", IsRequired = true)] public string ActionPattern { get { return this["actionPattern"] as string; } set { this["actionPattern"] = value; } } [ConfigurationProperty("algorithm", IsRequired = true)] public string Algorithm { get { return this["algorithm"] as string; } set { this["algorithm"] = value; } } [ConfigurationProperty("algorithmKey", IsRequired = true)] public string AlgorithmKey { get { return this["algorithmKey"] as string; } set { this["algorithmKey"] = value; } } protected override object CreateBehavior() { var hmac = HMAC.Create(Algorithm); if (hmac == null) { throw new ArgumentException(string.Format("Algorithm of type ({0}) is not supported.", Algorithm)); } if (string.IsNullOrEmpty(AlgorithmKey)) { throw new ArgumentException("AlgorithmKey cannot be null or empty."); } hmac.Key = Encoding.UTF8.GetBytes(AlgorithmKey); return new SigningBehavior(hmac, ActionPattern); } } public class SigningBehavior : IEndpointBehavior { private HMAC algorithm; private string actionPattern; public SigningBehavior(HMAC algorithm, string actionPattern) { this.algorithm = algorithm; this.actionPattern = actionPattern; } public void Validate(ServiceEndpoint endpoint) { } public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { clientRuntime.MessageInspectors.Add(new SigningMessageInspector(algorithm, actionPattern)); } } public class SigningMessageInspector : IClientMessageInspector { private readonly HMAC Signer; private readonly Regex ActionRegex; public SigningMessageInspector(HMAC algorithm, string actionPattern) { Signer = algorithm; ActionRegex = new Regex(actionPattern); } public void AfterReceiveReply(ref Message reply, object correlationState) { } public object BeforeSendRequest(ref Message request, IClientChannel channel) { var operation = GetOperation(request.Headers.Action); var timeStamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"); var toSignBytes = Encoding.UTF8.GetBytes(operation + timeStamp); var sigBytes = Signer.ComputeHash(toSignBytes); var signature = Convert.ToBase64String(sigBytes); request.Headers.Add(MessageHeader.CreateHeader("AWSAccessKeyId", Helpers.NameSpace, Helpers.AWSAccessKeyId)); request.Headers.Add(MessageHeader.CreateHeader("Timestamp", Helpers.NameSpace, timeStamp)); request.Headers.Add(MessageHeader.CreateHeader("Signature", Helpers.NameSpace, signature)); return null; } private string GetOperation(string request) { var match = ActionRegex.Match(request); var val = match.Groups["action"]; return val.Value; } }
To use this, you do not need to make any changes to the existing code, you can even put the signature code as a whole, if necessary. You just need to configure the configuration section like this (note: the version number is important, it does not load or start without code matching)
<system.serviceModel> <extensions> <behaviorExtensions> <add name="signer" type="WebServices.Amazon.SigningExtension, AmazonExtensions, Version=1.3.11.7, Culture=neutral, PublicKeyToken=null" /> </behaviorExtensions> </extensions> <behaviors> <endpointBehaviors> <behavior name="AWSECommerceBehaviors"> <signer algorithm="HMACSHA256" algorithmKey="..." actionPattern="\w:\/\/.+/(?<action>.+)" /> </behavior> </endpointBehaviors> </behaviors> <bindings> <basicHttpBinding> <binding name="AWSECommerceServiceBinding" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"> <readerQuotas maxDepth="32" maxStringContentLength="16384" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" /> <security mode="Transport"> <transport clientCredentialType="None" proxyCredentialType="None" realm="" /> <message clientCredentialType="UserName" algorithmSuite="Default" /> </security> </binding> </basicHttpBinding> </bindings> <client> <endpoint address="https://ecs.amazonaws.com/onca/soap?Service=AWSECommerceService" behaviorConfiguration="AWSECommerceBehaviors" binding="basicHttpBinding" bindingConfiguration="AWSECommerceServiceBinding" contract="WebServices.Amazon.AWSECommerceServicePortType" name="AWSECommerceServicePort" /> </client> </system.serviceModel>