Invalid signature when connecting to VitaDock API with Scribe library - oauth

I am using grails plugin: oauth 2.1.0 to connect to oauth APIs.
Vitadock requires HMACSHA256 to encode base signature string, so I created a HMACSha256SignatureService.groovy to do it and customize TargetScaleApi.groovy
HMACSha256SignatureService.groovy
import javax.crypto.*
import javax.crypto.spec.*
import org.apache.commons.codec.binary.*
import org.scribe.exceptions.*
import org.scribe.services.SignatureService
import org.scribe.utils.*
public class HMACSha256SignatureService implements SignatureService {
private static final String EMPTY_STRING = "";
private static final String CARRIAGE_RETURN = "\r\n";
private static final String UTF8 = "UTF-8";
private static final String HMAC_SHA256 = "HMACSHA256";
private static final String METHOD = "HMAC-SHA256";
/**
* {#inheritDoc}
*/
public String getSignature(String baseString, String apiSecret, String tokenSecret) {
try {
println baseString
Preconditions.checkEmptyString(baseString, "Base string cant be null or empty string");
Preconditions.checkEmptyString(apiSecret, "Api secret cant be null or empty string");
return doSign(baseString, OAuthEncoder.encode(apiSecret) + '&' + OAuthEncoder.encode(tokenSecret));
}
catch (Exception e) {
throw new OAuthSignatureException(baseString, e);
}
}
private String doSign(String toSign, String keyString) throws Exception {
SecretKeySpec key = new SecretKeySpec((keyString).getBytes(UTF8), HMAC_SHA256);
Mac mac = Mac.getInstance(HMAC_SHA256);
mac.init(key);
byte[] bytes = mac.doFinal(toSign.getBytes(UTF8));
String a = new String(Base64.encodeBase64(bytes)).replace(CARRIAGE_RETURN, EMPTY_STRING)
println a
return a;
}
public String getSignatureMethod() {
return METHOD;
}
}
TargetScaleApi.groovy
import org.scribe.builder.api.DefaultApi10a
import org.scribe.model.Token
import org.scribe.services.SignatureService
class TargetScaleApi extends DefaultApi10a {
private static final String AUTHORIZE_URL = "https://vitacloud.medisanaspace.com/auth?oauth_token=%s"
#Override
public String getAccessTokenEndpoint() {
return "https://vitacloud.medisanaspace.com/auth/accesses/verify"
}
#Override
public String getAuthorizationUrl(Token requestToken) {
return String.format(AUTHORIZE_URL, requestToken.getToken());
}
#Override
public String getRequestTokenEndpoint() {
return "https://vitacloud.medisanaspace.com/auth/unauthorizedaccesses"
}
#Override
public SignatureService getSignatureService() {
return new HMACSha256SignatureService();
}
}
But I received a error message: invalid signature.
<b>message</b>Invalid signature (jBbmlITCOBuIN3KfVB8glzv1sftrx1v7MvNyAJkiGTU%3D, expected: Ia21vjqskdBXrRE%2BngpHqaP4GJV3hfUGOt0ksGVcgk0%3D) [Base Parameter String: oauth_consumer_key=V5BiK7kzVcefBVfJ1htu13vfreWZNDPnkzx4DG67UBG6lNe0dZ1DUClKk5XM1Y1L&oauth_nonce=897870535&oauth_signature_method=HMAC-SHA256&oauth_timestamp=1372069427&oauth_version=1.0, Base Signature String: POST&https%3A%2F%2Fvitacloud.medisanaspace.com%2Fauth%2Funauthorizedaccesses&oauth_consumer_key%3DV5BiK7kzVcefBVfJ1htu13vfreWZNDPnkzx4DG67UBG6lNe0dZ1DUClKk5XM1Y1L%26oauth_nonce%3D897870535%26oauth_signature_method%3DHMAC-SHA256%26oauth_timestamp%3D1372069427%26oauth_version%3D1.0] [authorization = OAuth oauth_callback="http%3A%2F%2Flocal.mydatainnet.axonactive.vn%3A8080%2Faa-mdin-web-client-2.0.1%2Foauth%2Fcallback%3Fprovider%3Dtargetscale", oauth_signature="jBbmlITCOBuIN3KfVB8glzv1sftrx1v7MvNyAJkiGTU%3D", oauth_version="1.0", oauth_nonce="897870535", oauth_signature_method="HMAC-SHA256", oauth_consumer_key="V5BiK7kzVcefBVfJ1htu13vfreWZNDPnkzx4DG67UBG6lNe0dZ1DUClKk5XM1Y1L", oauth_timestamp="1372069427", content-type = application/x-www-form-urlencoded, cache-control = no-cache, pragma = no-cache, user-agent = Java/1.6.0_25, host = vitacloud.medisanaspace.com, accept = text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2, connection = keep-alive, content-length = 0, ]
Thanks for any help
Hang Dinh

I believe that VitaDock implements Oauth 1.0 (https://github.com/Medisana/vitadock-api/wiki/Definitions). If you are using a plugin geared towards oauth 2.1.0 maybe that could be the source of the error.

Related

Snowflake SDKTest: Error:(137, 49) java: cannot find symbol variable PublicKeyReader location: class net.snowflake.ingest.example.SDKTest

I am not able to run the SDKTest class following this documentation:
https://docs.snowflake.net/manuals/user-guide/data-load-snowpipe-rest-load.html
I am using IntelliJ and Java SDK 12.0.
Error:(137, 49) java: cannot find symbol
symbol: variable PublicKeyReader
location: class net.snowflake.ingest.example.SDKTest
Can anybody help me with this?
BR,
Here is the running copy of SDK Test code :
package com.snowflake.SalesforceTestCases;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.util.Base64;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import net.snowflake.ingest.SimpleIngestManager;
import net.snowflake.ingest.connection.HistoryRangeResponse;
import net.snowflake.ingest.connection.HistoryResponse;
public class SDKTest {
private static final String PRIVATE_KEY_FILE = "<Path to your p8 file>/rsa_key.p8";
public static class PrivateKeyReader {
public static PrivateKey get(String filename) throws Exception {
File f = new File(filename);
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int) f.length()];
dis.readFully(keyBytes);
dis.close();
String encrypted = new String(keyBytes);
String passphrase = "Snowflake";
encrypted = encrypted.replace("-----BEGIN ENCRYPTED PRIVATE KEY-----", "");
encrypted = encrypted.replace("-----END ENCRYPTED PRIVATE KEY-----", "");
EncryptedPrivateKeyInfo pkInfo = new EncryptedPrivateKeyInfo(Base64.getMimeDecoder().decode(encrypted));
PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray());
SecretKeyFactory pbeKeyFactory = SecretKeyFactory.getInstance(pkInfo.getAlgName());
PKCS8EncodedKeySpec encodedKeySpec = pkInfo.getKeySpec(pbeKeyFactory.generateSecret(keySpec));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey encryptedPrivateKey = keyFactory.generatePrivate(encodedKeySpec);
return encryptedPrivateKey;
}
}
private static HistoryResponse waitForFilesHistory(final SimpleIngestManager manager, Set<String> files)
throws Exception {
ExecutorService service = Executors.newSingleThreadExecutor();
class GetHistory implements Callable<HistoryResponse> {
private Set<String> filesWatchList;
GetHistory(Set<String> files) {
this.filesWatchList = files;
}
String beginMark = null;
public HistoryResponse call() throws Exception {
HistoryResponse filesHistory = null;
while (true) {
Thread.sleep(5000);
HistoryResponse response = manager.getHistory(null, null, beginMark);
if (response.getNextBeginMark() != null) {
beginMark = response.getNextBeginMark();
}
if (response != null && response.files != null) {
for (HistoryResponse.FileEntry entry : response.files) {
// if we have a complete file that we've
// loaded with the same name..
String filename = entry.getPath();
if (entry.getPath() != null && entry.isComplete() && filesWatchList.contains(filename)) {
if (filesHistory == null) {
filesHistory = new HistoryResponse();
filesHistory.setPipe(response.getPipe());
}
filesHistory.files.add(entry);
filesWatchList.remove(filename);
// we can return true!
if (filesWatchList.isEmpty()) {
return filesHistory;
}
}
}
}
}
}
}
GetHistory historyCaller = new GetHistory(files);
// fork off waiting for a load to the service
Future<HistoryResponse> result = service.submit(historyCaller);
HistoryResponse response = result.get(2, TimeUnit.MINUTES);
return response;
}
public static void main(String[] args) {
final String account = "<Snowflake Account Name >";
final String hostName = "<Snowflake Account URL>";
final String user = "<Snowflake Account Username>";
final String pipe = "<Snowflake Pipe Path>";
try {
final long oneHourMillis = 1000 * 3600L;
String startTime = Instant.ofEpochMilli(System.currentTimeMillis() - 4 * oneHourMillis).toString();
PrivateKey privateKey = PrivateKeyReader.get(PRIVATE_KEY_FILE);
//PublicKey publicKey = PublicKeyReader.get(PUBLIC_KEY_FILE);
#SuppressWarnings("deprecation")
SimpleIngestManager manager = new SimpleIngestManager(account, user, pipe, privateKey, "https", hostName, 443);
Set<String> files = new TreeSet<>();
files.add("<filename>");
manager.ingestFiles(manager.wrapFilepaths(files), null);
HistoryResponse history = waitForFilesHistory(manager, files);
System.out.println("Received history response: " + history.toString());
String endTime = Instant.ofEpochMilli(System.currentTimeMillis()).toString();
HistoryRangeResponse historyRangeResponse = manager.getHistoryRange(null, startTime, endTime);
System.out.println("Received history range response: " + historyRangeResponse.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
My understanding of the error is that the compiler does not know what the variable PublicKeyReader references. Perhaps the variable was declared incorrectly in the code or there was a misspelling? There is a sample Java program in the documentation you mentioned, but there is also a sample program here if it helps:
https://github.com/snowflakedb/snowflake-ingest-java
References for Error:
What does a "Cannot find symbol" compilation error mean?
https://www.thoughtco.com/error-message-cannot-find-symbol-2034060

How can I convert an Object to Json in a Rabbit reply?

I have two applications communicating with each other using rabbit.
I need to send (from app1) an object to a listener (in app2) and after some process (on listener) it answer me with another object, now I am receiving this error:
ClassNotFound
I am using this config for rabbit in both applications:
#Configuration
public class RabbitConfiguration {
public final static String EXCHANGE_NAME = "paymentExchange";
public final static String EVENT_ROUTING_KEY = "eventRoute";
public final static String PAYEMNT_ROUTING_KEY = "paymentRoute";
public final static String QUEUE_EVENT = EXCHANGE_NAME + "." + "event";
public final static String QUEUE_PAYMENT = EXCHANGE_NAME + "." + "payment";
public final static String QUEUE_CAPTURE = EXCHANGE_NAME + "." + "capture";
#Bean
public List<Declarable> ds() {
return queues(QUEUE_EVENT, QUEUE_PAYMENT);
}
#Autowired
private ConnectionFactory rabbitConnectionFactory;
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(rabbitConnectionFactory);
}
#Bean
public DirectExchange exchange() {
return new DirectExchange(EXCHANGE_NAME);
}
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate r = new RabbitTemplate(rabbitConnectionFactory);
r.setExchange(EXCHANGE_NAME);
r.setChannelTransacted(false);
r.setConnectionFactory(rabbitConnectionFactory);
r.setMessageConverter(jsonMessageConverter());
return r;
}
#Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
private List<Declarable> queues(String... nomes) {
List<Declarable> result = new ArrayList<>();
for (int i = 0; i < nomes.length; i++) {
result.add(newQueue(nomes[i]));
if (nomes[i].equals(QUEUE_EVENT))
result.add(makeBindingToQueue(nomes[i], EVENT_ROUTING_KEY));
else
result.add(makeBindingToQueue(nomes[i], PAYEMNT_ROUTING_KEY));
}
return result;
}
private static Binding makeBindingToQueue(String queueName, String route) {
return new Binding(queueName, DestinationType.QUEUE, EXCHANGE_NAME, route, null);
}
private static Queue newQueue(String nome) {
return new Queue(nome);
}
}
I send the message using this:
String response = (String) rabbitTemplate.convertSendAndReceive(RabbitConfiguration.EXCHANGE_NAME,
RabbitConfiguration.PAYEMNT_ROUTING_KEY, domainEvent);
And await for a response using a cast to the object.
This communication is between two different applications using the same rabbit server.
How can I solve this?
I expected rabbit convert the message to a json in the send operation and the same in the reply, so I've created the object to correspond to a json of reply.
Show, please, the configuration for the listener. You should be sure that ListenerContainer there is supplied with the Jackson2JsonMessageConverter as well to carry __TypeId__ header back with the reply.
Also see Spring AMQP JSON sample for some help.

AWS Cognito user authentication Missing required parameter SRP_A

I am trying to use AWS Cognito services for user authentication through ruby SDK.
I could able to sign_up, confirm sign_up process using the methods
resp = client.sign_up({ client_id: "ClientIdType",
secret_hash: "SecretHashType",
username: "UsernameType",
password: "PasswordType",
user_attributes: [{ name:"AttributeNameType",
value: "AttributeValueType",
}],
validation_data: [{
name: "AttributeNameType",
value: "AttributeValueType",
}]
})
and confirm_sign_up using
resp = client.confirm_sign_up({client_id: "ClientIdType",
secret_hash: "SecretHashType",
username: "UsernameType",
confirmation_code: "ConfirmationCodeType"
})
But while trying to sign in the user through initiate_auth I am getting an error Missing required parameter SRP_A
cog_provider.initiate_auth({client_id: "xxxxxxxxx", auth_parameters: { username: "xxx", password: "xxx"}, auth_flow: "USER_SRP_AUTH"})
What does SRP_A indicate where to find it.
I have searched for this problem and It is suggested to use the admin_initiate_auth method for signing in a user which I don't believe a best practice.
Yes, SRP_A is a large integer as defined by the Secure Remote Password Protocol. Are you trying to do SRP or just authenticate with username and password. For username/password authentication you should use the AdminInitiateAuth operation.
In our SDKs, you can see the parameters that need to be computed and passed. Take for example the Javascript SDK:
https://github.com/aws/amazon-cognito-identity-js/blob/master/src/CognitoUser.js#L152
Or in the Android SDK:
https://github.com/aws/aws-sdk-android/blob/master/aws-android-sdk-cognitoidentityprovider/src/main/java/com/amazonaws/mobileconnectors/cognitoidentityprovider/CognitoUser.java#L2123
For AWS Java SDK:
here is the class to manage this:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.math.pro.ak.util.cognito;
import com.amazonaws.AmazonClientException;
import com.amazonaws.util.StringUtils;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
/**
*
* #author marcus
*/
public class AuthenticationHelper {
private BigInteger a;
private BigInteger A;
private String poolName;
public AuthenticationHelper(String userPoolName) {
do {
a = new BigInteger(EPHEMERAL_KEY_LENGTH, SECURE_RANDOM).mod(N);
A = GG.modPow(a, N);
} while (A.mod(N).equals(BigInteger.ZERO));
if (userPoolName.contains("_")) {
poolName = userPoolName.split("_", 2)[1];
} else {
poolName = userPoolName;
}
}
public BigInteger geta() {
return a;
}
public BigInteger getA() {
return A;
}
private static final String HEX_N = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+ "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";
private static final BigInteger N = new BigInteger(HEX_N, 16);
private static final BigInteger GG = BigInteger.valueOf(2);
private static final BigInteger KK;
private static final int EPHEMERAL_KEY_LENGTH = 1024;
private static final int DERIVED_KEY_SIZE = 16;
private static final String DERIVED_KEY_INFO = "Caldera Derived Key";
private static final ThreadLocal<MessageDigest> THREAD_MESSAGE_DIGEST = new ThreadLocal<MessageDigest>() {
#Override
protected MessageDigest initialValue() {
try {
return MessageDigest.getInstance("SHA-256");
} catch (final NoSuchAlgorithmException e) {
throw new AmazonClientException("Exception in authentication", e);
}
}
};
private static final SecureRandom SECURE_RANDOM;
static {
try {
SECURE_RANDOM = SecureRandom.getInstance("SHA1PRNG");
final MessageDigest messageDigest = THREAD_MESSAGE_DIGEST.get();
messageDigest.reset();
messageDigest.update(N.toByteArray());
final byte[] digest = messageDigest.digest(GG.toByteArray());
KK = new BigInteger(1, digest);
} catch (final NoSuchAlgorithmException e) {
throw new AmazonClientException(e.getMessage(), e);
}
}
public byte[] getPasswordAuthenticationKey(String userId,
String userPassword,
BigInteger B,
BigInteger salt) {
// Authenticate the password
// u = H(A, B)
final MessageDigest messageDigest = THREAD_MESSAGE_DIGEST.get();
messageDigest.reset();
messageDigest.update(A.toByteArray());
final BigInteger u = new BigInteger(1, messageDigest.digest(B.toByteArray()));
if (u.equals(BigInteger.ZERO)) {
throw new AmazonClientException("Hash of A and B cannot be zero");
}
// x = H(salt | H(poolName | userId | ":" | password))
messageDigest.reset();
messageDigest.update(poolName.getBytes(StringUtils.UTF8));
messageDigest.update(userId.getBytes(StringUtils.UTF8));
messageDigest.update(":".getBytes(StringUtils.UTF8));
final byte[] userIdHash = messageDigest.digest(userPassword.getBytes(StringUtils.UTF8));
messageDigest.reset();
messageDigest.update(salt.toByteArray());
final BigInteger x = new BigInteger(1, messageDigest.digest(userIdHash));
final BigInteger s = (B.subtract(KK.multiply(GG.modPow(x, N)))
.modPow(a.add(u.multiply(x)), N)).mod(N);
Hkdf hkdf = null;
try {
hkdf = Hkdf.getInstance("HmacSHA256");
} catch (final NoSuchAlgorithmException e) {
throw new AmazonClientException(e.getMessage(), e);
}
hkdf.init(s.toByteArray(), u.toByteArray());
final byte[] key = hkdf.deriveKey(DERIVED_KEY_INFO, DERIVED_KEY_SIZE);
return key;
}
}
And call this the method:
userAuth.put("SRP_A", new AuthenticationHelper(request.getUsername()).getA().toString(16));

Custom extension element in Stanza for Smack 4.1.4

I am moving my android application from asmack-android library to Smack 4.1.4. I have some PacketExtensions in the asmack version of Smack, which uses PacketExtension and PacketExtensionProvider classes to handle. Since the PacketExtension is deprecated in Smack 4.1.4, I am confused among the classes and interfaces ExtensionElement, DataPacketExtension, ExtensionElementProvider , DefaultExtensionElement. Could any one of you give me an example of creating an extension which can be added with stanza and parse back...https://www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware /smack/packet/DefaultExtensionElement.htmlhttps://www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware /smack/provider/ExtensionElementProvider.html
Message message = new Message();
message.setStanzaId("923442621149");
message.setType(Type.chat);
message.setBody("shanraisshan");
Log.e("message --->", message.toXML().toString());
This will produce the following stanza
<message id='923442621149' type='chat'><body>shanraisshan</body></message>
1. CUSTOM EXTENSION STANZA TYPE-1
In order to generate below custom extension stanza
<message id='923442621149' type='chat'><body>shanraisshan</body>
<reply xmlns='shayan:reply' rText='this is custom attribute'/>
</message>
where reply is a custom extension, which contains
Element (reply)
Namespace (shayan:reply)
the list of default xmpp namespaces are available at Official XMPP website
Do following steps
1. Add ReplyExtension.java in your project
ReplyExtension.java
package com.xmpp.extensions;
import org.jivesoftware.smack.packet.DefaultExtensionElement;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.provider.EmbeddedExtensionProvider;
import org.jivesoftware.smack.util.XmlStringBuilder;
import java.util.List;
import java.util.Map;
/**
* Shayan Rais (http://shanraisshan.com)
* created on 9/7/2016
*/
public class ReplyExtension implements ExtensionElement {
public static final String NAMESPACE = "shayan:reply";
public static final String ELEMENT = "reply";
String rText = null;
static final String ATTRIBUTE_REPLY_TEXT = "rText";
#Override
public String getElementName() {
return ELEMENT;
}
#Override
public String getNamespace() {
return NAMESPACE;
}
#Override
public XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder(this);
xml.attribute(ATTRIBUTE_REPLY_TEXT, getReplyText());
xml.closeEmptyElement();
return xml;
}
//__________________________________________________________________________________________________
public void setReplyText(String _rText) {
rText = _rText;
}
public String getReplyText() {
return rText;
}
//__________________________________________________________________________________________________
public static class Provider extends EmbeddedExtensionProvider<ReplyExtension> {
#Override
protected ReplyExtension createReturnExtension(String currentElement, String currentNamespace, Map<String, String> attributeMap, List<? extends ExtensionElement> content) {
ReplyExtension repExt = new ReplyExtension();
repExt.setReplyText(attributeMap.get(ATTRIBUTE_REPLY_TEXT));
return repExt;
}
}
}
2. Register ReplyExtension in your Provider Manager
ProviderManager.addExtensionProvider(ReplyExtension.ELEMENT, ReplyExtension.NAMESPACE, new ReplyExtension.Provider());
FOR SENDING MESSAGES
You can generate the custom extension stanza TYPE-1 by using following code
Message message = new Message();
message.setStanzaId("923442621149");
message.setType(Type.chat);
message.setBody("shanraisshan");
//adding custom reply extension
ReplyExtension repExt = new ReplyExtension();
repExt.setReplyText("this is custom attribute");
message.addExtension(repExt);
Log.e("message --->", message.toXML().toString());
DURING RECEIVING MESSAGES
Now during receiving custom extension stanzas, you need to cast the extension to get attribute values.
//check for message with reply extension
ExtensionElement packetExtension = message.getExtension(ReplyExtension.NAMESPACE);
ReplyExtension repExt = (ReplyExtension)packetExtension;
if(repExt!=null) {
Log.e("--->", " --- LOG REPLY EXTENSION ---");
Log.e("--->", repExt.toXML() + "");
Log.e("--->", repExt.getReplyText() + ""); //this is custom attribute
}
_______________________________________________________
2. CUSTOM EXTENSION STANZA TYPE-2
In order to generate below custom extension stanza
<message id='923442621149' type='chat'><body>shanraisshan</body>
<reply xmlns='shayan:reply'><rText>this is custom attribute</rText></reply>
</message>
FOR SENDING MESSAGES
You can generate the custom extension stanza TYPE-2 by using following code
Message message = new Message();
message.setStanzaId("923442621149");
message.setType(Type.chat);
message.setBody("shanraisshan");
//adding custom reply extension
DefaultExtensionElement repExt = new DefaultExtensionElement("reply", "shayan:reply");
repExt.setValue("rText", "this is custom attribute");
message.addExtension(repExt);
Log.e("message --->", message.toXML().toString());
DURING RECEIVING MESSAGES
DefaultExtensionElement repExt = (DefaultExtensionElement) message.getExtension("shayan:reply");
if(repExt!=null) {
Log.e("--->", " --- LOG REPLY EXTENSION ---");
Log.e(getClass().getSimpleName(), repExt.getValue("rText"));
}
Finally figured it out.... Here is the solution for it...
import org.jivesoftware.smack.packet.DefaultExtensionElement;
public class IM_FileSharing_Extension extends DefaultExtensionElement implements
IM_Commons_Extension_FileSharing {
private String fileUrl;
private String fileType;
private String base64preview;
private String fileId;
private String fileSize;
public IM_FileSharing_Extension(String fileUrl, String fileType,
String base64preview, String fileId, String fileSize) {
super(FILE_TAG, XMLNS);
this.fileUrl = fileUrl;
this.fileType = fileType;
this.base64preview = base64preview;
this.fileId = fileId;
this.fileSize = fileSize;
}
#Override
public String toXML() {
StringBuilder sb = new StringBuilder("<" + FILE_TAG + " xmlns=\""
+ XMLNS + "\" ");
sb.append(FILE_URL + "=\"" + fileUrl + "\" ");
sb.append(FILE_ID + "=\"" + fileId + "\" ");
sb.append(FILE_TYPE + "=\"" + fileType + "\" ");
sb.append(FILE_SIZE + "=\"" + fileSize + "\">");
sb.append("<" + FILE_PREVIEW_TAG + ">" + base64preview + "</"
+ FILE_PREVIEW_TAG + ">");
sb.append("</" + FILE_TAG + ">");
return sb.toString();
}
public String getFileUrl() {
return fileUrl;
}
public void setFileUrl(String fileUrl) {
this.fileUrl = fileUrl;
}
public String getBase64preview() {
return base64preview;
}
public void setBase64preview(String base64preview) {
this.base64preview = base64preview;
}
public String getFileId() {
return fileId;
}
public void setFileId(String fileId) {
this.fileId = fileId;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
public String getFileSize() {
return fileSize;
}
public void setFileSize(String fileSize) {
this.fileSize = fileSize;
}
}
Provider for the above extension is as follows...
import java.io.IOException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.util.Log;
public class IM_FileSharingExtension_Provider extends
ExtensionElementProvider<IM_FileSharing_Extension> implements
IM_Commons_Extension_FileSharing {
static final String TAG = "file_extension";
#Override
public IM_FileSharing_Extension parse(XmlPullParser parser, int initialDepth)
throws XmlPullParserException, IOException, SmackException {
IM_FileSharing_Extension fileExtension = null;
boolean stop = false;
String n = null;
int evtType;
String fileUrl = null;
String fileType = null;
String fileId = null;
String fileSize = null;
while (!stop) {
evtType = parser.getEventType();
n = parser.getName();
Log.d(TAG, "n:" + n + " evt:" + evtType);
switch (evtType) {
case XmlPullParser.START_TAG:
if (FILE_TAG.equals(n)) {
fileUrl = parser.getAttributeValue("", FILE_URL);
fileType = parser.getAttributeValue("", FILE_TYPE);
fileId = parser.getAttributeValue("", FILE_ID);
fileSize = parser.getAttributeValue("", FILE_SIZE);
evtType = parser.next();
}
if (FILE_PREVIEW_TAG.equals(parser.getName())) {
String basePreview = parser.nextText();
fileExtension = new IM_FileSharing_Extension(fileUrl,
fileType, basePreview, fileId, fileSize);
}
evtType = parser.next();
break;
case XmlPullParser.END_TAG:
if (parser.getName().equals(FILE_TAG)) {
return fileExtension;
}
evtType = parser.next();
}
}
return null;
}
}
And should be added in Provider manager as following....
ProviderManager.addExtensionProvider(
IM_Commons_Extension_FileSharing.FILE_TAG,
IM_Commons_Extension_FileSharing.XMLNS,
new IM_FileSharingExtension_Provider());

Google OAuth2 returns “invalid_grant” - signing JWT issue

I've a OAuth2 java client (for Server to Server Applications) that is trying to create a JWT and then sign with a private key (from Google API console) - follow these pages https://developers.google.com/accounts/docs/OAuth2ServiceAccount. However, google OAuth2 keeps returning "invalid grant".
Here is the client code:
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.MessageFormat;
import org.apache.commons.codec.binary.Base64;
public class WebToken
{
private String iss;
private String prn;
private String scope;
private String aud;
private long exp;
private long iat;
private String keystoreLoc;
private String keyAlias;
private String password;
public WebToken(String iss, String prn, String scope, String aud, long exp, long iat, String p12file, String keyAlias, String password)
{
super();
this.iss = iss;
this.prn = prn;
this.scope = scope;
this.aud = aud;
this.exp = exp;
this.iat = iat;
this.keystoreLoc = p12file;
this.keyAlias = keyAlias;
this.password = password;
}
public static String encodeBase64(byte[] rawData)
{
byte[] data = Base64.encodeBase64(rawData);
return new String(data);
}
public String getToken()
throws Exception
{
String header = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";
//String header = "{\"alg\":\"RS256\"}";
String claimTemplate = "'{'\"iss\": \"{0}\", \"prn\": \"{1}\", \"scope\": \"{2}\", \"aud\": \"{3}\", \"exp\": {4}, \"iat\": {5}'}'";
StringBuffer token = new StringBuffer();
//Encode the JWT Header and add it to our string to sign
token.append(encodeBase64(header.getBytes("UTF-8")));
//Separate with a period
token.append(".");
//Create the JWT Claims Object
String[] claimArray = new String[6];
claimArray[0] = this.iss;
claimArray[1] = this.prn;
claimArray[2] = this.scope;
claimArray[3] = this.aud;
claimArray[4] = "" + this.exp;
claimArray[5] = "" + this.iat;
MessageFormat claims = new MessageFormat(claimTemplate);
String payload = claims.format(claimArray);
print(payload);
//Add the encoded claims object
token.append(encodeBase64(payload.getBytes("UTF-8")));
//Load the private key
PrivateKey privateKey = getPrivateKey(keystoreLoc, password);
byte[] sig = signData(token.toString().getBytes("UTF-8"), privateKey);
String signedPayload = encodeBase64(sig);
//Separate with a period
token.append(".");
//Add the encoded signature
token.append(signedPayload);
return token.toString();
}
private PrivateKey getPrivateKey(String keyFile, String password)
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException
{
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream(keyFile), password.toCharArray());
PrivateKey privateKey = (PrivateKey) keystore.getKey(keyAlias, password.toCharArray());
return privateKey;
}
public byte[] signData(byte[] data, PrivateKey privateKey)
throws InvalidKeyException, SignatureException, NoSuchAlgorithmException
{
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(data);
return signature.sign();
}
public static void print(String msg)
{
System.out.println(msg);
}
public static void main(String[] args)
throws Exception
{
String iss = "????????#developer.gserviceaccount.com";
String prn = "xxx#gmail.com";
String scope = "https://www.googleapis.com/auth/urlshortener";
String aud = "https://accounts.google.com/o/oauth2/token";
long iat = System.currentTimeMillis()/1000 - 60;
long exp = iat + 3600;
String keystoreLoc = "D:\\OAuth2\\google_sURL\\pkey1.p12";
String keyAlias = "privatekey";
String pwd = "notasecret";
WebToken jwt = new WebToken(iss, prn, scope, aud, exp, iat, keystoreLoc, keyAlias, pwd);
String token = jwt.getToken();
print(token);
// urn:ietf:params:oauth:grant-type:jwt-bearer
print("curl -vSs -X POST -d \"grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + token +
"\" -k https://accounts.google.com/o/oauth2/token");
}
}
Then use curl to get access token: curl -k -vSs -X POST -d "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" https://accounts.google.com/o/oauth2/token
< HTTP/1.1 400 Bad Request
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: Fri, 01 Jan 1990 00:00:00 GMT
< Date: Sun, 06 Jan 2013 01:02:21 GMT
< Content-Type: application/json
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< Server: GSE
< Transfer-Encoding: chunked
<
{
"error" : "invalid_grant"
}
Any idea on what's wrong?
============================
Finally I found the problems myself:
1) in the JWT, don't set "prn" (but you may need that depending on the requirement)
2) use "#developer.gserviceaccount.com" for "iss"
Here is the updated code:
import java.io.FileInputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.text.MessageFormat;
import org.apache.commons.codec.binary.Base64;
public class WebToken
{
private String iss;
private String scope;
private String aud;
private long exp;
private long iat;
private String keystoreLoc;
private String keyAlias;
private String password;
public WebToken(String iss, String scope, String aud, long exp, long iat, String p12file, String keyAlias, String password)
{
super();
this.iss = iss;
this.scope = scope;
this.aud = aud;
this.exp = exp;
this.iat = iat;
this.keystoreLoc = p12file;
this.keyAlias = keyAlias;
this.password = password;
}
/**
* Performs base64-encoding of input bytes.
*
* #param rawData * Array of bytes to be encoded.
* #return * The base64 encoded string representation of rawData.
*/
public static String encodeBase64(byte[] rawData)
{
String data = Base64.encodeBase64URLSafeString(rawData);
return data;
}
public String getToken()
throws Exception
{
String header = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";
String claimTemplate = "'{'\"iss\": \"{0}\", \"scope\": \"{1}\", \"aud\": \"{2}\", \"exp\": {3}, \"iat\": {4}'}'";
StringBuffer token = new StringBuffer();
//Encode the JWT Header and add it to our string to sign
token.append(encodeBase64(header.getBytes("UTF-8")));
//Separate with a period
token.append(".");
//Create the JWT Claims Object
String[] claimArray = new String[6];
claimArray[0] = this.iss;
claimArray[1] = this.scope;
claimArray[2] = this.aud;
claimArray[3] = "" + this.exp;
claimArray[4] = "" + this.iat;
MessageFormat claims = new MessageFormat(claimTemplate);
String payload = claims.format(claimArray);
print(payload);
//Add the encoded claims object
token.append(encodeBase64(payload.getBytes("UTF-8")));
//Load the private key
PrivateKey privateKey = getPrivateKey(keystoreLoc, password);
byte[] sig = signData(token.toString().getBytes("UTF-8"), privateKey);
String signedPayload = encodeBase64(sig);
//Separate with a period
token.append(".");
//Add the encoded signature
token.append(signedPayload);
return token.toString();
}
private PrivateKey getPrivateKey(String keyFile, String password)
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException
{
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream(keyFile), password.toCharArray());
PrivateKey privateKey = (PrivateKey) keystore.getKey(keyAlias, password.toCharArray());
return privateKey;
}
public byte[] signData(byte[] data, PrivateKey privateKey)
throws InvalidKeyException, SignatureException, NoSuchAlgorithmException
{
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(data);
return signature.sign();
}
public static void print(String msg)
{
System.out.println(msg);
}
public static void main(String[] args)
throws Exception
{
String iss = "<client id email>#developer.gserviceaccount.com";
String scope = "https://www.googleapis.com/auth/urlshortener";
String aud = "https://accounts.google.com/o/oauth2/token";
long iat = (System.currentTimeMillis()/1000)-60;
long exp = iat + 3600;
String keystoreLoc = "<privatekey>.p12";
String keyAlias = "privatekey";
String pwd = "notasecret";
WebToken jwt = new WebToken(iss, scope, aud, exp, iat, keystoreLoc, keyAlias, pwd);
String token = jwt.getToken();
print(token);
print("\\curl\\curl -vSs -X POST -H \"Content-Type: application/x-www-form-urlencoded\"" +
" -d \"grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=" + token +
"\" -k https://accounts.google.com/o/oauth2/token");
}
}

Resources