How to create HMACSHA256 api signature in Salesforce Apex - cryptography

Can somebody help in creating HMACSHA256 api signature in apex using crypto class. Corresponding java code is given below :-
public static void main(String[] args) throws GeneralSecurityException, IOException {
String secretKey = "secretKey";
String salt = "0123456789";
String generateHmacSHA256Signature = generateHmacSHA256Signature(salt, secretKey);
System.out.println("Signature: " + generateHmacSHA256Signature);
String urlEncodedSign = URLEncoder.encode(generateHmacSHA256Signature, "UTF-8");
System.out.println("Url encoded value: " + urlEncodedSign);
}
public static String generateHmacSHA256Signature(String data, String key) throws GeneralSecurityException {
byte[] hmacData = null;
try {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(secretKey);
hmacData = mac.doFinal(data.getBytes("UTF-8"));
return new Base64Encoder().encode(hmacData);
} catch (UnsupportedEncodingException e) {
throw new GeneralSecurityException(e);
}
}
Thanks in advance

http://boards.developerforce.com/t5/Apex-Code-Development/How-to-create-HMACSHA256-api-signature/td-p/551055
I think that'll do it for you?
Copied here for posterity (in case the link dies)
AKK's answer:
"Re: How to create HMACSHA256 api signature
‎12-28-2012 02:58 AM
Sorry for unformatted code, actually I was looking into how to format but didn't find anything in mozilla and when login through chrome editor appeared.
I got the signature right using below code maybe this helps someone :-
public void genrateSignature() {
String salt = String.valueOf(Crypto.getRandomInteger());
String secretKey = 'secret_key';
String signature = generateHmacSHA256Signature(salt, secretKey);
System.debug('Signature : '+signature);
}
private static String generateHmacSHA256Signature(String saltValue, String secretKeyValue) {
String algorithmName = 'HmacSHA256';
Blob hmacData = Crypto.generateMac(algorithmName, Blob.valueOf(saltValue), Blob.valueOf(secretKeyValue));
return EncodingUtil.base64Encode(hmacData);
}
Thanks"

Related

Error code 706 when signing PDF using Web Agent in Java

When testing the Web Agent sample in Java, I am getting an error reply
<?xml version="1.0" encoding="utf-8"?>
<response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" type="error">
<Error>
<returnCode>706</returnCode>
<errorMessage>Value cannot be null.
Parameter name: s</errorMessage>
</Error>
</response>
I followed the Ruby example in the CoSign Web Agent samples and the documentation
I have used the demo.pdf file provided in the sample.
This is the XML (from test app) sent in the POST request (the <content></content> has the Base64 encoded PDF, but omitted because of length).
<?xml version="1.0" encoding="utf-8" ?>
<request>
<Logic>
<allowAdHoc>true</allowAdHoc>
<workingMode>pull</workingMode>
<enforceReason>false</enforceReason>
</Logic>
<Url>
<finishURL>http://localhost:64956/retrieveSignedFile.aspx</finishURL>
</Url>
<Document>
<fileID>1234567890</fileID>
<contentType>pdf</contentType>
<content>{BASE64 encoded pdf content}</content>
</Document>
</request>
The following is the java code I have used:
public class CoSignTest {
private static final String INPUT = "D:\\tmp\\demo.pdf";
private static final String PRECONTENT = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
"<request>\n" +
" <Logic>\n" +
" <allowAdHoc>true</allowAdHoc>\n" +
" <workingMode>pull</workingMode>\n" +
" <enforceReason>false</enforceReason>\n" +
" </Logic>\n" +
" <Url>\n" +
" <finishURL>http://localhost:64956/retrieveSignedFile.aspx</finishURL>\n" +
" </Url>\n" +
" <Document>\n" +
" <fileID>1234567890</fileID>\n" +
" <contentType>pdf</contentType>\n" +
" <content>";
private static final String POSTCONTENT = "</content>\n" +
" </Document>\n" +
"</request>";
private static final String POST_URL = "https://webagentdev.arx.com/Sign/UploadFileToSign";
private static final String PULL_URL = "https://webagentdev.arx.com/Sign/DownloadSignedFileG";
public static final int TIMEOUT = 300000;
public static void main(String[] args) throws Exception {
InputStream is = new FileInputStream(INPUT);
String content = PRECONTENT + new String(Base64.encodeBase64(loadResource(is)), "UTF-8") + POSTCONTENT;
System.out.println(content);
String reply = new String(sendDocForProcessing(URLEncoder.encode(content, "UTF-8")));
System.out.println(reply);
System.out.println("DONE");
}
private static String sendDocForProcessing(String content) throws Exception {
HttpClient client = null;
HttpMethodBase method = null;
SimpleHttpConnectionManager mgr = new SimpleHttpConnectionManager();
String reply = "";
try {
mgr.getParams().setConnectionTimeout(TIMEOUT);
mgr.getParams().setSoTimeout(TIMEOUT);
client = new HttpClient(mgr);
method = new PostMethod(POST_URL);
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(1, false));
method.getParams().setParameter("http.socket.timeout", TIMEOUT);
client.getHttpConnectionManager().getParams().setConnectionTimeout(TIMEOUT);
client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
method.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
method.getParams().setParameter("inputXML", content);
client.executeMethod(method);
reply = new String(method.getResponseBody());
} catch (Exception e) {
e.printStackTrace();
} finally {
if(method != null) {
method.releaseConnection();
}
client = null;
mgr.shutdown();
}
if (isSigningSuccessful(reply)) {
return reply;
} else {
throw new Exception("Failed in signing the document. Error: " + reply);
}
}
private static boolean isSigningSuccessful(String reply) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new ByteArrayInputStream(reply.getBytes()));
Element elem = doc.getDocumentElement();
String type = elem.getAttribute("type");
return !"error".equals(type);
}
public static byte[] loadResource(InputStream in) {
if (in == null) {
return new byte[0];
}
try {
int indice, tempIndice;
byte[] tempArr;
byte[] mainArr = new byte[0];
byte[] byteArr = new byte[65535];
for (indice = 0; (indice = in.read(byteArr)) > 0;) {
tempIndice = mainArr.length + indice;
tempArr = new byte[tempIndice];
System.arraycopy(mainArr, 0, tempArr, 0, mainArr.length);
System.arraycopy(byteArr, 0, tempArr, mainArr.length, indice);
mainArr = tempArr;
}
in.close();
return mainArr;
} catch (Exception e) {
e.printStackTrace();
}
return new byte[0];
}
}
The XML elements are case sensitive and must be passed as shown in the documentation (e.g. Document instead of document, Auth instead of auth and so on). In addition, your XML request is missing the finishURL parameter which is mandatory.
Also note that some parameters in your XML request are obsolete. See the updated request parameter list in the link above. A sample XML is available here.
Thanks for adding your Java code. Note that the HttpClient instance is configured incorrectly and as a result the http-post request is sent empty. Take a look at the modifications I did in your sendDocForProcessing function in order to properly post the XML content:
private static String sendDocForProcessing(String content) throws Exception {
HttpClient client = null;
PostMethod method = null;
String reply = "";
try {
client = new HttpClient();
method = new PostMethod(POST_URL);
method.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
NameValuePair[] data = { new NameValuePair("inputXML", content) };
method.setRequestBody(data);
client.executeMethod(method);
reply = method.getResponseBodyAsString();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(method != null) {
method.releaseConnection();
}
}
if (isSigningSuccessful(reply)) {
return reply;
} else {
throw new Exception("Failed in signing the document. Error: " + reply);
}
}
The content passed to the above function should not be URL-encoded as it is already done by the HttpClient library.
In addition, when analyzing the response, I suggest you to check the value of the returnCode element rather than the type property. The response is always of type 'error'.
Also note that the function name isSigningSuccessful is misleading as this stage is still prior to the act of signing.

MvvmCross HTTP DownloadCache with authentication

In my app, the user need to be authenticated on the server to download data using WebAPIs.
The MvvmCross DownloadCache plugin seems to handle only basic HTTP GET queries. I can't add my authentication token in the url as it's a big SAML token.
How can I add a HTTP header to queries done through DownloadCache plugin ?
With the current version I think I should inject my own IMvxHttpFileDownloader but I'm looking for an easier solution. Injecting my own MvxFileDownloadRequest would be better (not perfect) but it doesn't have an interface...
I'm able to do it registering a custom IWebRequestCreate for a custom scheme (http-auth://).
It's a bit ugly to transform urls from my datasource but it does the job.
public class AuthenticationWebRequestCreate : IWebRequestCreate
{
public const string HttpPrefix = "http-auth";
public const string HttpsPrefix = "https-auth";
private static string EncodeCredential(string userName, string password)
{
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
string credential = userName + ":" + password;
return Convert.ToBase64String(encoding.GetBytes(credential));
}
public static void RegisterBasicAuthentication(string userName, string password)
{
var authenticateValue = "Basic " + EncodeCredential(userName, password);
AuthenticationWebRequestCreate requestCreate = new AuthenticationWebRequestCreate(authenticateValue);
Register(requestCreate);
}
public static void RegisterSamlAuthentication(string token)
{
var authenticateValue = "SAML2 " + token;
AuthenticationWebRequestCreate requestCreate = new AuthenticationWebRequestCreate(authenticateValue);
Register(requestCreate);
}
private static void Register(AuthenticationWebRequestCreate authenticationWebRequestCreate)
{
WebRequest.RegisterPrefix(HttpPrefix, authenticationWebRequestCreate);
WebRequest.RegisterPrefix(HttpsPrefix, authenticationWebRequestCreate);
}
private readonly string _authenticateValue;
public AuthenticationWebRequestCreate(string authenticateValue)
{
_authenticateValue = authenticateValue;
}
public WebRequest Create(System.Uri uri)
{
UriBuilder uriBuilder = new UriBuilder(uri);
switch (uriBuilder.Scheme)
{
case HttpPrefix:
uriBuilder.Scheme = "http";
break;
case HttpsPrefix:
uriBuilder.Scheme = "https";
break;
default:
break;
}
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uriBuilder.Uri);
request.Headers[HttpRequestHeader.Authorization] = _authenticateValue;
return request;
}
}

Using OAuthWebSecurity with Salesforce

I'm trying to get an ASP.NET MVC site to accept Salesforce as an authentication provider, but I am not having any luck. I'll start out with the IAuthenticationClient I have so far:
public class SalesForceOAuth2Client : OAuth2Client
{
private readonly String consumerKey;
private readonly String consumerSecret;
#if DEBUG
private const String BaseEndpoint = #"https://test.salesforce.com";
#else
private const String BaseEndpoint = #"https://login.salesforce.com";
#endif
private const String AuthorizeEndpoint = BaseEndpoint + #"/services/oauth2/authorize";
private const String TokenEndpoint = BaseEndpoint + #"/services/oauth2/token";
private const String RevokeEndpoint = BaseEndpoint + #"/services/oauth2/revoke";
public SalesForceOAuth2Client(String consumerKey, String consumerSecret)
: base("SalesForce")
{
if (String.IsNullOrWhiteSpace(consumerKey))
{
throw new ArgumentNullException("consumerKey");
}
if (String.IsNullOrWhiteSpace(consumerSecret))
{
throw new ArgumentNullException("consumerSecret");
}
this.consumerKey = consumerKey;
this.consumerSecret = consumerSecret;
}
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
String redirect_url = returnUrl.AbsoluteUri;
// Hack to work-around the __provider__ & __sid__ query parameters,
// but it is ultimately useless.
/*String state = String.Empty;
Int32 q = redirect_url.IndexOf('?');
if (q != -1)
{
state = redirect_url.Substring(q + 1);
redirect_url = redirect_url.Substring(0, q);
}*/
var builder = new UriBuilder(AuthorizeEndpoint);
builder.Query = "response_type=code"
+ "&client_id=" + HttpUtility.UrlEncode(this.consumerKey)
+ "&scope=full"
+ "&redirect_uri=" + HttpUtility.UrlEncode(redirect_url)
// Part of the above hack (tried to use `state` parameter)
/*+ (!String.IsNullOrWhiteSpace(state) ? "&state=" + HttpUtility.UrlEncode(state) : String.Empty)*/;
return builder.Uri;
}
protected override IDictionary<String, String> GetUserData(String accessToken)
{
// I am not sure how to get this yet as everything concrete I've
// seen uses the service's getUserInfo call (but this service relies
// heavily on a username, password, token combination. The whole point
// of using oatuh is to avoid asking the user for his/her credentials)
// more information about the original call:
// http://www.salesforce.com/us/developer/docs/api/Content/sforce_api_calls_getuserinfo.htm
// Return static information for now
//TODO: Get information dynamically
return new Dictionary<String, String>
{
{ "username", "BradChristie" },
{ "name", "Brad Christie" }
};
}
protected override String QueryAccessToken(Uri returnUrl, String authorizationCode)
{
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(TokenEndpoint);
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.Method = "POST";
using (StreamWriter streamWriter = new StreamWriter(webRequest.GetRequestStream()))
{
streamWriter.Write("grant_type=authorization_code");
streamWriter.Write("&client_id=" + HttpUtility.UrlEncode(this.consumerKey));
streamWriter.Write("&client_secret=" + HttpUtility.UrlEncode(this.consumerSecret));
streamWriter.Write("&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.AbsoluteUri));
streamWriter.Write("&code=" + HttpUtility.UrlEncode(authorizationCode));
streamWriter.Flush();
}
HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
if (webResponse.StatusCode == HttpStatusCode.OK)
{
using (StreamReader streamReader = new StreamReader(webResponse.GetResponseStream()))
{
String response = streamReader.ReadToEnd();
var queryString = HttpUtility.ParseQueryString(response);
return queryString["access_token"];
}
}
return String.Empty;
}
}
The primary problem is that redirect_uri != Callback Url.
Salesforce enforces the callback URL you supply in the application configuration to match exactly to the value provided in redirect_uri of QueryAccessToken. Unfortunately OAuthWebSecurity relies on DotNetOpenAuth.AspNet, and that library appends two query parameters: __provider__ and __sid__. If I try to remove those (see the hack in GetServiceLoginUrl), obviously the login fails because the hand-back doesn't know how to continue on with the request without knowing which provider to use.
To work around this I did notice that the request call accepts an optional state parameter which is (essentially) there for passing things back and forth across the request/callback. However, with the dependence on __provider__ and __sid__ being their own keys having data=__provider__%3DSalesForce%26__sid__%3D1234567890 is useless.
Is there a work-around without having to fork/recompile the Microsoft.Web.WebPages.OAuth library and modify the OAuthWebSecurity.VerifyAuthenticationCore(HttpContextBase, String) method to look at data first, then continue on to OpenAuthSecurityMananer.GetProviderName?
Also, in case the registration mattered (AuthConfig.cs):
OAuthWebSecurity.RegisterClient(
new SalesForceOAuth2Client(/*consumerKey*/, /*consumerSecret*/),
"SalesForce",
new Dictionary<String, Object>()
);
Update (11.01.2013)
I just got a response back from Salesforce. It looks like they don't know how to implement 3.1.2 of the RFC which means that any query parameters you send in with the return_uri are not only ignored, but prohibited (at least when dynamic in nature). So, it looks like I can't use a library that works on every other platform and follows the standard--i have to create my own.
Sigh.

Calling Webmethod containing byte array (as in paramaeter)parameter with ksoap2 on android

There is WEB service written on C# with next method:
[WebMethod]
public string ByteArrTest(byte[] Buffer)
{
if (Buffer == null) return "buffer is null";
else return Buffer.Length.ToString() + " is buffer length";
}
i 'ld like call this method from android device using Ksoap2 library alike belove (simplified):
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
SoapEnvelope.VER11);
envelope.dotNet = true;
new MarshalBase64().register(envelope);
envelope.encodingStyle = SoapEnvelope.ENC;
SoapObject request = new SoapObject(this.getNameSpace(), this.getMethodName());
PropertyInfo pi4 = new PropertyInfo();
pi4.setName("Buffer");
byte [] b="this text".getBytes();
pi4.setValue(b);
pi4.setType(byte[].class);
// request.addProperty("buffer", "bytes".getBytes);
request.addProperty(pi4);
envelope.setOutputSoapObject(request);
HttpTransportSE androidHttpTransport =
new HttpTransportSE(this.getURL());//
androidHttpTransport.call(this.getSoapAction(), envelope);
Object response = envelope.getResponse();
//next implementation
Responce always is "buffer is null"
what is incorrect or wrong?
Thanks for any attention
Posting the whole of your method in Android calling the web service would help more.
I'm using KSoap in an Android project I'm currently working on and I'm retrieving strings. Heres one of my methods modified to match what you need:
private static String NAMESPACE = "http://tempuri.org/";
private static String SOAP_ACTION = "http://tempuri.org/";
private static final String URL = "Your url link to your web services asmx file";
public static String ByteArrTestCall(byte[] t) {
String resTxt = null;
SoapObject request = new SoapObject(NAMESPACE, "ByteArrTest");
// Add the property to request object
request.addProperty("Buffer", t);
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
envelope.dotNet = true;
envelope.setOutputSoapObject(request);
HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);
androidHttpTransport.debug = true;
try
{
androidHttpTransport.call(SOAP_ACTION+"ByteArrTest", envelope);
SoapPrimitive receivedString = (SoapPrimitive) envelope.getResponse();
resTxt = receivedString.toString();
}
catch(Exception e)
{
resTxt = androidHttpTransport.requestDump;
return e.toString() + resTxt;
}
return resTxt;
}

How to login Gmail with Apache's HttpClient?

I want to login my gmail and get the contact list automatically with httpClient,
I've tryed the method described in the page below:
Android: How to login into webpage programmatically, using HttpsURLConnection
but once it ran to:
String cookie = response.getFirstHeader("Set-Cookie").getValue();
a java.lang.NullPointerException was catched.
I thought it was because the page was moved temporarily, then I coded my code like this:
private static String uriLogin = "https://mail.google.com";
private static String uriContacts = "https://mail.google.com/mail/shva=1#contacts";
// the account was registered just for test:
private static String myAcc = "httpclient.test";
private static String myPwd = "testpassword";
public static void main(String[] args) throws ClientProtocolException,
IOException, InterruptedException {
DefaultHttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(uriLogin);
List<NameValuePair> params = new ArrayList<NameValuePair>(3);
params.add(new BasicNameValuePair("Email", myAcc));
params.add(new BasicNameValuePair("Passwd", myPwd));
params.add(new BasicNameValuePair("signIn", "Sign in"));
post.setEntity(new UrlEncodedFormEntity(params));
redp("url is:: ", post.getEntity());
HttpResponse rsp = client.execute(post);
if (rsp.getStatusLine().getStatusCode() >= 400) {// returns 302 if
// success
System.err.println("failed to get the web page!!!");
System.err.println("status: " + rsp.getStatusLine());
System.exit(-1);
}
redp("status is:: ", rsp.getStatusLine());
redp("heads of rsp :: ", "");
pHeads(rsp);
redp("content is:: ", EntityUtils.toString(rsp.getEntity()));
String redirect = rsp.getLastHeader("Location").getValue();
HttpGet get = new HttpGet(redirect);
rsp = client.execute(get);
String cookie = rsp.getFirstHeader("Set-Cookie").getValue();
redp("cookie is:: ", cookie);
HttpGet getContacts = new HttpGet(uriContacts);
getContacts.setHeader("Cookie", cookie);
redp("heads of get [contacts]:: ", "");
pHeads(getContacts);
client.getConnectionManager().shutdown();
client = new DefaultHttpClient(); // without these 2 lines,
// "java.lang.IllegalStateException"
// will be catched
rsp = client.execute(getContacts);
redp("heads of rsp (new) ::", "");
pHeads(rsp);
InputStream istream = rsp.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(
istream));
String line;
p("联系人列表: ");
while ((line = reader.readLine()) != null) {
p(line);
}
reader.close();
istream.close();
}// main
public static void pHeads(HttpMessage msg) {
Header[] headers = msg.getAllHeaders();
for (int i = 0; i < headers.length; i++)
p(headers[i].getName() + ": " + headers[i].getValue());
}
public static void p(Object o) {
System.out.println(o);
}
public static void redp(String head, Object o) throws InterruptedException {
System.err.println(head);
if (o.equals("") || o.equals(null))
return;
Thread.sleep(100);
System.out.println(o);
}
}
`
but it still doesn't work... Any help would be great~~
[BTW, I saw a some people says on the Internet that httpClient was not very acceptable for this kind of job, could you tell me in what kind of project HttpClient is most used?]
From what I've seen, You receive the nullPointerEception when you get to response.getLastHeader("Location").getValue();
it looks like there is no "Location" tag in the GMail html header. When Java fails to find this, it just throws "I can't find it"
What you want is the redirect after the POST occurs.
At a quick glance, its something like:
https://accounts.google.com/ServiceLogin?service=mail&passive=true&rm=false&continue=http://mail.google.com/mail/&scc=1&ltmpl=default&ltmplcache=2
but you should double check this.
Hope this helps.