Why is verification failing for a JWS signature? - java

I'm trying to sign the message with a detached payload using the Nimbus JOSE JWT library in Java. The verification goes through locally but whenever I try to send it to the server using Postman I get: "The signature header x-jws-signature was parsed and has a valid JOSE header that complies with the specification. However, the signature itself could not be verified"
JWSSigner signer = new RSASSASigner(privateKey);
HashMap<String, Object> criticalParameters = new HashMap<>();
criticalParameters.put("http://openbanking.org.uk/iat", 1501497671);
criticalParameters.put("http://openbanking.org.uk/iss", orgId);
criticalParameters.put("http://openbanking.org.uk/tan", "openbankingtest.org.uk");
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.PS256)
.type(JOSEObjectType.JOSE)
.keyID(keyID)
.criticalParams(criticalParameters.keySet())
.customParams(criticalParameters)
.build();
// With encoding the payload
JWSObject jwsObject = new JWSObject(header, payload);
jwsObject.sign(signer);
String jws = jwsObject.serialize(true);
JWSObject parsedJWSObject = JWSObject.parse(jws, payload);
if (parsedJWSObject.verify(new RSASSAVerifier(publicKey, criticalParameters.keySet()))) {
System.out.println(parsedJWSObject.serialize(true));
} else {
System.out.println("Invalid");
}
//=============================
// Without encoding the payload
Base64URL signature = signer.sign(header, (header.toBase64URL().toString() + "." + payload).getBytes());
JWSVerifier verifier = new RSASSAVerifier(publicKey, criticalParameters.keySet());
boolean isValid = verifier.verify(header, (header.toBase64URL().toString() + "." + payload).getBytes(), signature);
System.out.println(header.toBase64URL().toString() + ".." + signature.toString());
System.out.println(isValid);
//=============================
Both of the functions successfully sign and verify the JWS but for some reason, it doesn't work. If it helps, I'm trying to access the Open Banking API.

Got a similar problem very recently. I would suggest you to check the following:
Is the payload in the request exactly the same as the one used for the JW signature (without escaping or formatting characters)?
What's the order of the JSON properties in the payload and does the financial entity you are trying to interact with have specific requirements when it comes to the order of those JSON fields?
I know it's very questionable to expect the json properties in the payload to be in a specific order, but by experience I found out that some open banking implementations are assuming a specific order (not even alphabetical) and they will fail with that error when the order is not the one they expect.

JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.PS256)
.type(JOSEObjectType.JOSE)
.keyID(keyID)
.criticalParams(criticalParameters.keySet())
.customParams(criticalParameters)
.build();
//simplyfy your payload json string before..remove all spaces.
Gson gson = new GsonBuilder().disableHtmlEscaping().create();
JsonElement el = JsonParser.parseString(payload);
String simplePayload=gson.toJson(el);
// With encoding the payload
Payload detachedPayload =new Payload(new Base64URL(simplePayload).toString());
JWSObject jwsObject = new JWSObject(header, detachedPayload );
jwsObject.sign(signer);
String jws = jwsObject.serialize(true);
JWSObject parsedJWSObject = JWSObject.parse(jws, detachedPayload );

Related

AWS S3 Pre Signed URL with Metadata

Here is the code snippet where I am trying to upload a document with some custom metadata using AWS S3 pre signed URL.
try (CloseableHttpClient httpClient = HttpClients.createSystem()) {
var tempFile = File.createTempFile(document.getName(), FilenameUtils.getExtension(document.getOriginalFilename()));
document.transferTo(tempFile);
var fileEntity = new FileEntity(tempFile);
var httpPut = new HttpPut(url);
// Here 403 httpPut.setHeader("x-amz-meta-title", "Test Title");
httpPut.setEntity(fileEntity);
var response = httpClient.execute(httpPut);
log.info("HTTP response code {}", response.getStatusLine().getStatusCode());
} catch (IOException e) {
log.error("Oops! Error", e);
}
Unfortunately setting a custom header as above throws 403. It works fine when I take out the header. Even works okay when you add an irrelevant metadata key say "abc" - Obviously not adding metadata, but returns 200 OK. The issue seems only when you specify "x-amz-meta-".
Any thoughts?
The metadata of a presigned url are set, when you create it. They are prefilled and can't be modified by the client.
The only thing you can do is set an expected value and optionally a condition. In the condition you can specify to reject the upload, if the expected meta-data value isn't supplied.

Convert GetEntity().GetContent() from Java to C#

I need to convert the following code from Java to C# when I'm using restAPI in C#.
In java :
HttpGet statusGet = new HttpGet(fileUrl);
statusGet.setHeader("X-API-TOKEN", API_TOKEN);
HttpResponse response = httpClient.execute(statusGet);
// Extract exported file
ZipInputStream zs = new ZipInputStream(response.getEntity().getContent());
In C# this is what I have:
var client1 = new RestClient(fileUrl);
var request1 = new RestRequest(Method.GET);
request1.AddHeader("X-API-TOKEN", "API_TOKEN");
request1.AddHeader("content-type", "application/json");
request1.AddParameter("application/json", "{\n\t\"format\" : \"csv\",\n\t\"surveyId\" : \"_surveyId\"\n}", ParameterType.RequestBody);
IRestResponse responsedata = client1.Execute(request1);
var download=client1.DownloadData(request1);
MemoryStream stream = new MemoryStream(download);
ZipInputStream zs = new ZipInputStream(stream);
using (ZipFile zip1 = ZipFile.Read(zs))
I have no clue how to implement response.getEntity().getContent(). I believe it is getting the Stream(Containing a zip file?)
Updated: So I get the byte array from client1.DownloadData(request1), looks like it is not right to convert it to stream (has readtimeout exception). and it will not be able to read from zipfile.read
Thank you so much for your help
Are you getting any specific errors? It looks like you are implementing this using RestSharp. Have you followed their examples and read through their documentation?
I have not personally used this third-party solution, but immediately on their front page they have the following example that does exactly what you are trying to do:
var client = new RestClient("http://example.com");
// client.Authenticator = new HttpBasicAuthenticator(username, password);
var request = new RestRequest("resource/{id}", Method.POST);
request.AddParameter("name", "value"); // adds to POST or URL querystring based on Method
request.AddUrlSegment("id", "123"); // replaces matching token in request.Resource
// easily add HTTP Headers
request.AddHeader("header", "value");
// add files to upload (works with compatible verbs)
request.AddFile(path);
// execute the request
IRestResponse response = client.Execute(request);
var content = response.Content; // raw content as string
// or automatically deserialize result
// return content type is sniffed but can be explicitly set via RestClient.AddHandler();
RestResponse<Person> response2 = client.Execute<Person>(request);
var name = response2.Data.Name;
// easy async support
client.ExecuteAsync(request, response => {
Console.WriteLine(response.Content);
});
// async with deserialization
var asyncHandle = client.ExecuteAsync<Person>(request, response => {
Console.WriteLine(response.Data.Name);
});
// abort the request on demand
asyncHandle.Abort();
It looks like you would want to access the IRestResponse.Content property, or to deserialize using the RestClient.Execute<T>(RestRequest request) function.

Format of private blob storage url, for GET file. Rest API

Microsoft states that getting a Blob is just a normal http get https://myaccount.blob.core.windows.net/mycontainer/myblob. But how do I format the string when I have an account + shared key?
I know there is an Azure SDK, but i'm creating an "add-on" to an existing java ee system, and cannot run in Azure, so I'm using REST Api. This is what i've tried so far:
String account = "myaccount";
String key = "243fedfsdf23f4f";
String protocol = "http";
String storageConnectionString = String.format("DefaultEndpointsProtocol=%s;AccountName=%s;AccountKey=%s", protocol, account, key);
System.out.println(storageConnectionString);
URL url = new URL("https://mysite.azureweb.com/myfile.txt");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
if (conn.getResponseCode() != 200) {
throw new IOException(conn.getResponseMessage());
}
// Buffer the result into a string
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = rd.readLine()) != null) {
sb.append(line);
}
rd.close();
conn.disconnect();
The string probably needs some Base64 encoding?
Update
The Http request looks like GET https://myAccount.blob.core.windows.net/myDir/myfile.txt HTTP/1.1
x-ms-date: Thu, 01 Oct 2015 12:56:11 GMT
x-ms-version: 2015-02-21
Authorization: SharedKey myAccount:asdfkjsladjfsdf827fhwf298f924f92723dfh23f273f2h7h4f
Host: myAccount.blob.core.windows.net
I "just" need to package this into a request to get a file in /mydir/myfile.txt
There are two access types for Azure Storage. One via Shared Keys and other other one via Shared Access Signatures.
Shared Keys give access to the whole storage account. Per storage account you have two shared keys and they are both equal. Usually you never give your shared keys away. Typically you only use them on the server side not in apps on the client side.
You only want to give someone access to a single file. Therefor using shared keys would be the wrong solution.
Shared Access Signatures give you the possibility to create a (REST) request, that is limited to certain files or containers. You can choose the privileges like write, read, delete etc. And you define a timeframe where the access is valid. For Shared Access Signatures you have two options: a) ad-hoc and b) policy-based. Ad-hoc Shared Access Signatures cannot be easily revoked (you could delete the file or invalidate the shared key which you used to create the Shared Access Signature). Policy-based Shared Access Signatures can easily be revoked by deleting the policy.
If you do not want to use the Azure SDK, you can create your own Shared Access Signatures. How to construct them is explained in the following link:
Constructing a Service SAS
There are also samples.
Service SAS Examples
Your file is stored in a BLOB. So you have to use the service BLOB. On the samples page you find the following BLOB sample.
signedstart=2013-08-16
signedexpiry=2013-08-17
signedresource=c
signedpermissions=r
signature=dD80ihBh5jfNpymO5Hg1IdiJIEvHcJpCMiCMnN/RnbI=
signedidentifier=YWJjZGVmZw==
signedversion=2013-08-15
responsecontent-disposition=file; attachment
responsecontent-type=binary
StringToSign = r + \n
2013-08-16 + \n
2013-08-17 + \n
/myaccount/pictures + \n
YWJjZGVmZw== + \n
2013-08-15 + \n
+ \n
file; attachment + \n
+ \n
+ \n
binary
HMAC-SHA256(URL.Decode(UTF8.Encode(StringToSign))) = a39+YozJhGp6miujGymjRpN8tsrQfLo9Z3i8IRyIpnQ=
Finally you get a URL for your REST request.
GET https://myaccount.blob.core.windows.net/pictures/profile.jpg?sv=2013-08-15&st=2013-08-16&se=2013-08-17&sr=c&sp=r&rscd=file;%20attachment&rsct=binary &sig=YWJjZGVmZw%3d%3d&sig=a39%2BYozJhGp6miujGymjRpN8tsrQfLo9Z3i8IRyIpnQ%3d HTTP/1.1
Have a look on the two pages for the full explanation.
There is a simple way to generate the SAS for getting files in the private container by using the Azure Storage SDK.
Following the sample code below to generat the SAS key and format the URL:
String accountName = "<your_account_name>";
String accountKey = "<your_account_key>";
String containerName = "<your_private_container_name>";
String blobFileName = "<your_blob_file_name>";
String storageConnectionString = String.format("DefaultEndpointsProtocol=%s;AccountName=%s;AccountKey=%s", "https", accountName, accountKey);
CloudStorageAccount account = CloudStorageAccount.parse(storageConnectionString);
CloudBlobClient blobClient = account.createCloudBlobClient();
CloudBlobContainer container = blobClient.getContainerReference(containerName);
CloudBlockBlob blob = container.getBlockBlobReference(blobFileName);
SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();
GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
calendar.setTime(new Date());
policy.setSharedAccessStartTime(calendar.getTime());
calendar.add(Calendar.HOUR, 1);
policy.setSharedAccessExpiryTime(calendar.getTime());
policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ));
String sas = blob.generateSharedAccessSignature(policy, null);
System.out.println(sas)
String urlstr = String.format("https://%s.blob.core.windows.net/%s/%s?%s", accountName, containerName, blobFileName, sas);
System.out.println(urlstr);
For details, you can refer to the doc https://msdn.microsoft.com/en-us/library/hh875756.aspx.

I am not able to get signature from java sever side(java servlet)

I take the signature on the server side(java servlet) to pass it to the html client.
This is the code:
Cloudinary cloudinary = new Cloudinary(Cloudinary.asMap("cloud_name", "dvg8fiorp", "api_key", "742866863611915", "api_secret", "zF-GJqVyWjih_MqQGsYeSOVVmJ8"));
String timestamp = (new Long(System.currentTimeMillis() / 1000L)).toString();
Map<String, Object> params = new HashMap<String, Object>();
Map options = Cloudinary.emptyMap();
boolean returnError = Cloudinary.asBoolean(options.get("return_error"), false);
String apiKey = Cloudinary.asString(options.get("api_key"), cloudinary.getStringConfig("api_key"));
if (apiKey == null)
throw new IllegalArgumentException("Must supply api_key");
String apiSecret = Cloudinary.asString(options.get("api_secret"), cloudinary.getStringConfig("api_secret"));
if (apiSecret == null)
throw new IllegalArgumentException("Must supply api_secret");
params.put("callback", "http://localhost:8080/SimpleServlet/js/cloudinary_js/html/cloudinary_cors.html");
params.put("timestamp", timestamp);
String expected_signature = cloudinary.apiSignRequest(params, apiSecret);
Unfortunately, the last line of code fail and i don't know how get signature to pass it to html client
First, note that your API Secret Should not be publicly revealed. I strongly advise that you generate a new pair or API key and secret from your Security settings page (https://cloudinary.com/console/settings/security).
Second, this actually generated the correct signature, I managed to have successful uploads with it.
In order to further understand the issue:
What kind of an error response did you get?
Please share your upload request code.
This is the server's response:
java.lang.NoSuchMethodError: org.apache.commons.lang.StringUtils.join(Ljava/util/Collection;Ljava/lang/String;)Ljava/lang/String;
com.cloudinary.Cloudinary.apiSignRequest(Cloudinary.java:93)
us.souther.simple.UploadServersideServlet

Translate Python requests code to Java (UniRest)

I am working with the Snapchat API to make a Java Client. I am using an endpoint which takes the following parameters from an HTTP POST:
{
username: snapchat username,
timestamp: UNIX timestamp,
media_id: random string,
type: 0,
req_token: request token,
data: encrypted data
}
I have no problem generating the params hash, and I have the data as a File object.
In Python I have confirmed that the following works:
f = open('encrypted.jpg')
params = { ... all params besides data ... }
files = { 'data' : f }
r = requests.post(path, params, files=files)
That Python code gets me a 200. I am using tokens and data/files generated by Java code, so the data sent is identical.
In Java I am doing the following with UniRest:
Map<String, Object> params = ... same params ...;
File f = new File('encrypted.jpg');
HttpRequestWithBody req = Unirest.post(path);
req.fields(params);
req.field("data", f);
HttpResponse<String> resp = req.asString();
However this gives me a 500 response from the server. How can I write Java that emulates the Python exactly? Or how can I snoop my own network traffic to see the difference in what the code for each is doing? Seems crazy to me that one works and the other does not.
try to chain methods, i.e.
req = Unirest.post(path).fields(params).field("data", f);
or change the lines:
req = req.fields(params);
req = req.field("data", f);

Categories

Resources