Preface: I've been trying to do XML signature verification on an HTTP response, and I need help! All code is .NET 4.0 using C#.
So here's what I'm trying to accomplish:
Create a signed XML document on the server
Send the signed XML as body of an HTTP response
Client receives the response and verifies that the signature is valid.
Server-side, I create the XML and load it into an XmlDocument. I then sign this XmlDocument object (using this example code from MSDN) and build a string from this signed XML. This string is what I send as the HTTP response body.
When my client application receives the response, it pulls the body of the response out and passes it to my signature verification function. This function builds an XmlDocument from the string, creates a SignedXml object from the XmlDocument, and retrieves the Signature to verify. Almost all this code is taken from MSDN as well (here).
Seems straightforward, right? Well my verification fails every time. I know that it's not a problem with the signing/verifying code. I've tested it in a separate app where the XML it loads is from a file, and it works perfectly. I'm even using the exact same XML to test my client/server code.
Thus, I believe the problem lies in the step where XmlDocument is converted to a string or the string is converted back to XmlDocument.
XmlDocument -> string -> XmlDocument
I've done the following things to try to make it easier to the signature to verify:
Remove all tabs, newlines, carriage returns from the XML before I create the XmlDocument.
Ensured that the encoding of the document is explicitly set at UTF-8 (i know from previous threads that this can cause an issue if not set).
Tried generating the strings in two different ways (from OuterXML of the XmlDocument & also by using XmlWriter and StringWriter).
Visually verified that the XML sent from the server is the exact same as that loaded by the client.
If you have any idea on how to remedy this problem, please help! I can post code if desired, but the only code that might be worth seeing is how I generate the string from the XmlDocument.
An old question but I figured I would would answer it for anyone else who might have encountered a similar issue. The problem was in the encoding of the string as it was sent back via the HTTP response. However, I remedied this by writing the XmlDocument directly to the response stream instead of converting it to a string first. Like such:
public void ProcessRequest(HttpContext context)
{
// a bunch of request handling logic
//...
HttpResponse response = context.Response;
XmlDocument signedXML = getTheSignedXMLData(); //the XML
signedXML.PreserveWhitespace = true;
signedXML.Save(response.Output);
}
This solved by encoding issues and the signature verifies correctly.
Related
Cheers,
I have a soap webservice written in c#, that takes in an XMLDocument as parameter...
[WebMethod]
[SoapDocumentMethod(ResponseNamespace = "http://ns.ctb.nl/flex/2012-1")]
public string stuurReflexBericht(XmlDocument m) //XElement m
{
//do something
}
and Im using Boomerang, an extension for Google Chrome to test the service.
Boomerang creates this Request Body:
<x:Envelope xmlns:x="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://ctb.nl/webservices">
<x:Header/>
<x:Body>
<web:stuurReflexBericht>
<web:m>
My XML Body
</web:m>
</web:stuurReflexBericht>
</x:Body>
</x:Envelope>
and this works, the service receives the xml message. However, the consumer of the service wants to send the message as:
<x:Envelope xmlns:x="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://ctb.nl/webservices">
<x:Header/>
<x:Body>
My XML Body
</x:Body>
</x:Envelope>
and when they do that, the XMLDocument is null.
The consumer does not want to change their code, so its up to me to make the adjustments.
I tried changing the parameter datatype from XMLDocument to string in the hopes that it would work, but it does not.
Any ideas on how to solve this?
TIA
I'm creating a sort of proxy service that needs to process calls containing XML from the body of a POST to my WebAPI service and then POST it on to another service.
The odd thing is when I receive the XML message from the POST the 1st part of the XML from the body is cut off. Initially I thought maybe buffer size, or message was too big so I cut out a lot of the XML test message being sent reducing what was being sent. However the XML is still cut off in the same place.
I've tried the following (2) methods to read the XML BODY in the WebAPI service and the result is the same:
var reader = new StreamReader(Request.Content.ReadAsStreamAsync().Result);
string originalMessage = reader.ReadToEnd();
and:
var result = "";
Request.Content.ReadAsStreamAsync().ContinueWith(x =>
{
using (var sr = new StreamReader(x.Result))
{
result = sr.ReadToEnd();
}
});
Here is a snippet the original XML:
<Message version="123" release="001" xmlns="http://www.mysite.com/schema">
<Header>
<To Att1="A">001</To>
<From Att2="B">002</From>
<ID>9876</ID>
Here is the beginning of the content after reading it in the WebAPI controller POST:
</To>
<From Att2="B">002</From>
<ID>9876</ID>
See how it starts at the 'closing' tag of the <To> element? That's obviously not the beginning of the XML that it was sent.
The even more peculiar thing is the 'Content Size' when inspected before it is sent and after is 4188 on both sides. Something else interesting is that I have an old fashion .asmx tester (as opposed to a Web API service) that does the identical thing. When I read the incoming XML message in that app using the following:
// Get raw request body
Stream receiveStream = HttpContext.Current.Request.InputStream;
// Move to beginning of input stream and read
receiveStream.Position = 0;
using (StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8))
{
// Load into XML document
xmlSoapRequest.Load(readStream);
}
...I see the full XML message. So not sure why the .asmx can read it but not the WebAPI service fully.
What am I doing wrong in my WebAPI POST call to where I can't see the full XML message that was sent in the body of the request?
Well I figured out the issue, but not 100% sure on the reason. The parameter I was using for the POST was the one right out of the box:
public HttpResponseMessage Post([FromBody]string value)
I changed it to take the request as a parameter on the POST instead:
public HttpResponseMessage Post(HttpRequestMessage request)
When I did the 2nd option above, I began to get the entire XML body from the request as expected.
TL;DR Düsseldor is encoded to Düsseldor, Düsseldorf isn't encoded (by the default MVC encoding) why?
I'm building an api with soap access. When I get a request I html encode all the data.
Just before I end the current request I copy the Response.Stream and save it for logging.
I noticed that Düsseldorf is encoded by my api to "Düsseldorf". This is expected.
But in my logs it shows Düsseldorf encoded as "Düsseldorf" ( double encoded ) my logs saves after the MVC serializes writes to the response stream.
I think this is because soap encodes values before sending them over the wire.
So my next test was to turn off html encoding in my api.
So this time Düsseldorf was giving to the soap serializer.
But it was encoded as Düsseldorf?
EDIT: When I say I turn on / off the encoding what I mean is each field in the request I use HttpUtility.HtmlEncode("some field"). And save that to the database. Then I return the data with an MVC view like so:
public ActionResult CreateAccessCode ( CreateAccessCodeRequest request )
{
var response = new Response (request);
return new ContentResult (response);
}
My question is when the ContentResult method runs, it seems to do some encoding of its own and I was wondering what that was?
SOAP uses XML - HTML and XML require slightly different encoding, but it may or may not be a problem in your case.
Much more likely your manual construction of XML is not correct. Please do not construct XML with string concatenation - XDocument/XmlDocument are much more suitable for that.
I'm using the Amazon .NET SDK to generate a pre-signed URL like this:
public System.Web.Mvc.ActionResult AsActionResult(string contentType, string contentDisposition)
{
ResponseHeaderOverrides headerOverrides = new ResponseHeaderOverrides();
headerOverrides.ContentType = contentType;
if (!string.IsNullOrWhiteSpace(contentDisposition))
{
headerOverrides.ContentDisposition = contentDisposition;
}
GetPreSignedUrlRequest request = new GetPreSignedUrlRequest()
.WithBucketName(bucketName)
.WithKey(objectKey)
.WithProtocol(Protocol.HTTPS)
.WithExpires(DateTime.Now.AddMinutes(6))
.WithResponseHeaderOverrides(headerOverrides);
string url = S3Client.GetPreSignedURL(request);
return new RedirectResult(url, permanent: false);
}
This works perfectly, except if my contentType contains a + in it. This happens when I try to get an SVG file, for example, which gets a content type of image/svg+xml. In this case, S3 throws a SignatureDoesNotMatch error.
The error message shows the StringToSign like this:
GET 1234567890 /blah/blabh/blah.svg?response-content-disposition=filename="blah.svg"&response-content-type=image/svg xml
Notice there's a space in the response-content-type, where it now says image/svg xml instead of image/svg+xml. It seems to me like that's what is causing the problem, but what's the right way to fix it?
Should I be encoding my content type? Enclose it within quotes or something? The documentation doesn't say anything about this.
Update
This bug has been fixed as of Version 1.4.1.0 of the SDK.
Workaround
This is a confirmed bug in the AWS SDK, so until they issue a fix I'm going with this hack to make things work:
Specify the content type exactly how you want it to look like in the response header. So, if you want S3 to return a content type of image/svg+xml, set it exactly like this:
ResponseHeaderOverrides headerOverrides = new ResponseHeaderOverrides();
headerOverrides.ContentType = "image/svg+xml";
Now, go ahead and generate the pre signed request as usual:
GetPreSignedUrlRequest request = new GetPreSignedUrlRequest()
.WithBucketName(bucketName)
.WithKey(objectKey)
.WithProtocol(Protocol.HTTPS)
.WithExpires(DateTime.Now.AddMinutes(6))
.WithResponseHeaderOverrides(headerOverrides);
string url = S3Client.GetPreSignedURL(request);
Finally, "fix" the resulting URL with the properly URL encoded value for your content type:
url = url.Replace(contentType, HttpUtility.UrlEncode(contentType));
Yes, it's a dirty workaround but, hey, it works for me! :)
Strange indeed - I've been able reproduce this easily, with the following observed behavior:
replacing + in the the URL generated by GetPreSignedURL() with its encoded form %2B yields a working URL/signature
this holds true, no matter whether / is replaced with its encoded form %2F or not
encoding the contentType upfront before calling GetPreSignedURL(), e.g. via the HttpUtility.UrlEncode Method, yields invalid signatures regardless of any variation of the generated URL
Given how long this functionality is available already, this is somewhat surprising, but I'd still consider it to be a bug - accordingly it might be best to inquiry about this in the Amazon Simple Storage Service forum.
Update: I just realized you asked the same question there already and the bug got confirmed indeed, so the correct answer can be figured out over time by monitoring the AWS team response ;)
Update: This bug has been fixed as of Version 1.4.1.0 of the SDK.
I'd like my WCF service to return an xml file that has been signed.
I found documentation that shows how to sign an XmlDocument on msdn, but since a WCF function can't return an XmlDocument I'm not sure if the following would work (similar to thisquestion)
public XmlElement GetXml() {
var doc = new XmlDocument();
// add data to doc
// sign doc
return doc.DocumentElement;
}
Would it still be possible to verify the signature of doc.DocumentElement if I added it to another XmlDocument after a client requested it? Is there a better way to do this?
Thanks!
XmlDocument is not decorated with DataContractAttribute and I cannot see why the object needs to be sent over the wire while the serialized form (text form) is all that is required.
I would design it as:
[OperationContract]
string GetFooXml();
And send the string. That is what WCF/XML is for, sending data as text whenever possible so that more kinds of clients can consume it.