I was making the server in c++ to connect to WebSocket but somehow, it was not connecting with the websocket. The WebSocket is showing the connection is closed and there is also some problem in the c++ server, as on second call to the server from WebSocket, it is showing the following error - double free or corruption (out). I have spend a lot of time on it. Here is the code:
c++
#include<iostream>
#include <string>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
using namespace std;
string getConnectionKey(char*);
void acceptConnection(int, const char*);
void readConnection(int);
void bail(char*);
string executeShellCommand(const string&);
string getBase64Encoded(string);
char *getClientKey(char*);
string getSHA1Hash(string);
int main()
{
char srvr_adr[] = "127.0.0.1";
char srvr_port[] = "9099";
struct sockaddr_in adr_srvr;
struct sockaddr_in adr_clnt;
socklen_t len_inet;
int s; // Server Socket
int c; // Client Socket
int z;
char *data;
char readdata[256];
int count = 2;
data = (char*)malloc(sizeof(char)*128);
s = socket(PF_INET, SOCK_STREAM, 0);
if(s == -1)
bail("socket()");
memset(&adr_srvr,0,sizeof(adr_srvr));
adr_srvr.sin_family = AF_INET;
adr_srvr.sin_port = htons(atoi(srvr_port));
if( strcmp(srvr_adr,"*")!=0)
{
adr_srvr.sin_addr.s_addr = inet_addr(srvr_adr);
if(adr_srvr.sin_addr.s_addr == INADDR_NONE)
bail(" INVALID ADRESS \n");
}
else /* WILD ADDRESS*/
adr_srvr.sin_addr.s_addr = INADDR_ANY;
len_inet = sizeof adr_srvr;
z = bind(s,(struct sockaddr*)&adr_srvr, len_inet);
if(z==-1)
bail("bind(2)");
z = listen(s,10);
if(z==-1)
bail("listen(2)");
for(;;)
{
len_inet = sizeof(adr_clnt);
c = accept(s, (struct sockaddr*)&adr_clnt,&len_inet);
if(c==-1)
bail("accept(2)");
readConnection(c);
close(c);
}
return 0;
}
void readConnection(int c)
{
int z;
char readdata[256];
// READING
z = read(c,readdata, sizeof(readdata)-1);
if(z==-1)
bail("read(2)");
else if(strlen(readdata)>0)
printf(" READ \n%s\n", readdata);
string key = getConnectionKey(readdata);
cout<<" KEY "<<key<<endl;
acceptConnection(c, key.c_str());
}
void acceptConnection(int c, const char *key)
{
int z;
char response[] = "HTTP/1.1 101 Switching Protocols\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Accept: ";
char *output;
output = (char*)malloc( sizeof(char) * ( strlen(key) + strlen(response) + 1) );
strcat(output, response);
strcat(output, key);
cout<<" output "<<output<<endl;
// WRITING
z = write(c, output, strlen(output));
if(z == -1)
bail("write(2)");
printf(" Connection Done \n");
}
string getConnectionKey(char *str)
{
char *start,*end,*key;
int len;
string s("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
// GET CLIENT KEY
key = getClientKey(str);
// Appending the key
s = key + s;
// SHA1 HASH
string out = getSHA1Hash(s);
//hashwrapper *h = new sha1wrapper();
//string out = h->getHashFromString(s);
// BASE 64 ENCODING
string encoded = getBase64Encoded(out);
//encoded = (char*)malloc(sizeof(char)*256);
//strcpy(encoded, getBase64Encoded(out) );
free(key);
//delete h;
return encoded;
}
char *getClientKey(char *str)
{
int len;
char *start,*end,*key;
start = strstr(str, "Sec-WebSocket-Key:");
if(start == NULL)
return false;
start += 17;
end = strstr(start, "==");
if(end == NULL)
return false;
end++;
while( !(*start>=65 && *start<=90 || *start >= 97 && *start<=122 || *start>=48 && *start<=57 || *start == '+' || *start=='/') )
start++;
len = end - start + 1;
key = (char*) malloc( sizeof(char) * (len+1) );
strncpy(key,start,len);
return key;
}
string getBase64Encoded(string s)
{
int len;
string str="";
len = s.length();
char *command;
for(int i=len-1 ; i>=1; i=i-2)
{
str = s.substr(i-1,2) + str;
str = "\\x" + str;
}
if(len%2==1)
{
str = s[0] + str;
str = "\\x" + str;
}
// making the command to be send to shell
str = "printf \"" + str ;
str = str + "\" | base64";
cout<<endl<<" STRING "<<str<<endl;
return executeShellCommand(str);
}
string getSHA1Hash(string str)
{
int len ;
string output;
str = "printf \""+str;
str = str +"\" | sha1sum";
cout<<str<<endl;
output = executeShellCommand(str);
return output.substr(0,output.length()-4);;
}
string executeShellCommand(const string& cmd)
{
FILE *fpipe;
if ( !(fpipe = (FILE*)popen(cmd.c_str(),"r")) )
{ // If fpipe is NULL
perror("Problems with pipe");
exit(1);
}
char buf[256] = "";
string line="";
while ( fgets( buf, sizeof buf, fpipe) )
{
if(strlen(buf)>0)
line.append(buf);
memset(buf, 0, sizeof(buf));
}
// CLOSE THE PIPE
pclose(fpipe);
return line;
}
void bail(char *on_what)
{
if(errno!=0)
{
fputs( strerror(errno), stderr);
fputs( ":", stderr);
}
fputs( on_what, stderr);
fputs("\n",stderr);
}
Here is the Websocket Code:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Chat</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<section id="content"></section>
<input id="message" type="text" tabindex="1"/>
<textarea id="show">
</textarea>
<script src="http://www.google.com/jsapi"></script>
<script>google.load("jquery", "1.3")</script>
<script src="http://jquery-json.googlecode.com/files/jquery.json-2.2.min.js"></script>
<!--script src="http://jquery-websocket.googlecode.com/files/jquery.websocket-0.0.1.js"></script-->
<script src="/js/jquery.websocket-0.0.1.js"></script>
<script type="text/javascript">
/*var ws = $.websocket("ws://127.0.0.1:9099/",
{
events: {
message: function(e)
{
alert("e.data");
$('#content').append(e.data + '<br>')
}
}
});*/
var websocketConnection = new WebSocket("ws://127.0.0.1:9099/");
websocketConnection.onopen = function(ev)
{
showmsg('Connected to the echo service');
};
websocketConnection.onerror = function(ev)
{
showmsg(" ERROR : ".ev.data);
}
websocketConnection.onclose = function(ev)
{
showmsg(" Connection Closed");
};
websocketConnection.onmessage = function(event)
{
showmsg(event.data);
$('#content').append(event.data+"<br>");
};
showmsg(" CURRENT STATE "+websocketConnection.readyState);
if(!websocketConnection)
showmsg(" object null ");
websocketConnection.send("Hello Echo Server");
$('#message').change(function(){
flag = ws.send('message', this.value);
if(!flag)
alert("not send");
this.value = '';
});
function showmsg(content)
{
$('#show').val(content+"<br>");
}
</script>
</body>
</html>
Please Help me out , what is the problem in the C++ and what is the response to be sent to the WebScoket.
This is one problem (in executeShellCommand() function):
command = (char*) malloc( sizeof(char) * cmd.length() );
line = (char*)malloc(sizeof(char)*256);
line[0] = '\0';
//cout<<" COMMAND "<<cmd<<endl;
strcpy(command, cmd.c_str() ); // Writes one beyond the end of the
// 'command' buffer as no space allocated
// for null terminator
You could just pass the cmd.c_str() directly to popen() instead of allocating and populating the command buffer for that purpose:
if ( !(fpipe = (FILE*)popen(cmd.c_str(),"r")) )
I would recommend replacing char* with std::string where possible and allow it to manage memory for you and use stack allocated buffers instead of dynamically allocating buffers if a std::string is not appropriate. For example:
std::string executeShellCommand(const std::string& cmd)
{
FILE *fpipe;
if ( !(fpipe = (FILE*)popen(cmd.c_str(),"r")) )
{ // If fpipe is NULL
perror("Problems with pipe");
exit(1);
}
char buf[256] = "";
std::string line;
while ( fgets( buf, sizeof buf, fpipe) )
{
line += buf;
memset(buf, 0, sizeof(buf));
}
// CLOSE THE PIPE
pclose(fpipe);
return line;
}
Related
I am trying to make a program that reads a string from a file in SPIFFS with 4 tab-separated things and then processes it into four char arrays to be used in another function. However, I get the error cannot convert 'char*' to 'char**' in assignment. Is there any idea why? Here's my code:
#include <string.h>
#include "FS.h"
#include "AdafruitIO_WiFi.h"
char *ssid;
char *pass;
char *aiduser;
char *aidkey;
// comment out the following two lines if you are using fona or ethernet
#include "AdafruitIO_WiFi.h"
//AdafruitIO_WiFi io(IO_USERNAME, IO_KEY, WIFI_SSID, WIFI_PASS);
void setupWifi(char* *aiduser, char* *aidkey, char* *ssid, char* *pass){
#define WIFIFILE "/config.txt"
int addr = 0;
bool spiffsActive = false;
if (SPIFFS.begin()) {
spiffsActive = true;
}
File f = SPIFFS.open(WIFIFILE, "r");
String str;
while (f.position()<f.size())
{
str=f.readStringUntil('\n');
str.trim();
}
// Length (with one extra character for the null terminator)
int str_len = str.length() + 1;
// Prepare the character array (the buffer)
char char_array[str_len];
// Copy it over
str.toCharArray(char_array, str_len);
const char s[2] = {9, 0};
/* get the first token */
aiduser = strtok(char_array, s);
aidpass = strtok(NULL, s);
ssid = strtok(NULL, s);
pass = strtok(NULL, s);
/* walk through other tokens
while( token != NULL ) {
printf( " %s\n", token );
token = strtok(NULL, s);
}*/
// RESULT: A thingy
}
void setup(){
setupWifi(&aiduser, &aidkey, &ssid, &pass);
AdafruitIO_WiFi io(aiduser, aidkey, ssid, pass);}
Also, I can't run the setupWifi function unless it is in setup or loop, but I can't make it in another setup because this is #included into another main file.
You get this error because of this:
void setupWifi(char* *aiduser, char* *aidkey, char* *ssid, char* *pass)
{
...
aiduser = strtok(char_array, s);
aidpass = strtok(NULL, s);
ssid = strtok(NULL, s);
pass = strtok(NULL, s);
}
This variables are double pointers, strtok returns a pointer to char, those
are not compatible types.
Because strtok returns char_array + some_offset and char_array is a local
variable in setupWifi, you need to do a copy for each of them and return the
copy instead. You can do it with strdup.
*aiduser = strdup(strtok(char_array, s));
*aidpass = strdup(strtok(NULL, s));
*ssid = strdup(strtok(NULL, s));
*pass = strdup(strtok(NULL, s));
I encourage you to always check the return value of strdup, because it can
return NULL.1
If your system does not have strdup, then you can write your own:
char *strdup(const char *text)
{
if(text == NULL)
return NULL;
char *copy = calloc(strlen(text) + 1, 1);
if(copy == NULL)
return NULL;
return strcpy(copy, text);
}
One last thing:
void setupWifi(char* *aiduser, char* *aidkey, char* *ssid, char* *pass);
It looks really awkward, never seen declaring double pointer this way. Much
easier to read would be
void setupWifi(char **aiduser, char **aidkey, char **ssid, char **pass);
Fotenotes
1While the syntax is correct, I still consider this bad practice,
because you should always check the return values of functions that return
pointers. If they return NULL, you cannot access the memory. This adds a
little bit of more code, but your program will not die of segfaults and it can
recover from the errors.
I'd also change your function to return 1 on success, 0 otherwise:
int parse_and_set(char *txt, const char *delim, char **var)
{
if(delim == NULL || var == NULL)
return 0;
char *token = strtok(txt, delim);
if(token == NULL)
return 0;
token = strdup(token);
if(token == NULL)
return NULL;
*var = token;
return 1;
}
void init_parse(char ***vars, size_t len)
{
for(size_t i = 0; i < len; ++i)
**(vars + i) = NULL;
}
int cleanup_parse(char ***vars, size_t len, int retval)
{
for(size_t i = 0; i < len; ++i)
{
free(**(vars + i));
**(vars + i) = NULL;
}
}
int setupWifi(char **aiduser, char **aidkey, char **ssid, char **pass)
{
if(aiduser == NULL || aidkey == NULL || ssid == NULL || pass == NULL)
return 0;
...
/* get the token token */
char **vars[] = { aiduser, aidkey, ssid, pass };
size_t len = sizeof vars / sizeof *vars;
init_parse(vars, len);
if(parse_and_set(char_array, s, aiduser) == 0)
return cleanup_parse(vars, len, 0);
if(parse_and_set(NULL, s, aidpass) == 0)
return cleanup_parse(vars, len, 0);
if(parse_and_set(NULL, s, ssid) == 0)
return cleanup_parse(vars, len, 0);
if(parse_and_set(NULL, s, pass) == 0)
return cleanup_parse(vars, len, 0);
...
return 1;
}
I am attempting to create a program to create a Markov chain but I am having pointer problems. When I run the Program I get a segmentation fault.
#include <stdio.h>
#include <cstring>
#include <cstdlib>
struct word;
struct nextword
{
word* sourceword;
word* next = 0;
};
int wordcount;
struct word
{
char* wordstr;
struct word* next = 0;
nextword* followingword = 0;
int nextwordcount = 0;
};
int main()
{
word* firstword = 0;
char * buffer = 0;
long length;
FILE * f = fopen ("alice.txt", "rb");
if (f)
{
fseek (f, 0, SEEK_END);
length = ftell (f);
fseek (f, 0, SEEK_SET);
buffer = (char *)malloc (length);
if (buffer)
{
fread (buffer, 1, length, f);
}
fclose (f);
}
if (buffer)
{
char wordbuffer[500];
int fileindex = 0;
while(fileindex < length-1)
{
int wordindex = 0;
while(buffer[fileindex] != ' ')
{
wordbuffer[wordindex] = buffer[fileindex];
wordindex++;
fileindex++;
}
if(wordindex != 0)
{
wordbuffer[wordindex] = '\0';
word* newword = (word*)malloc(sizeof(word));
char* newwordstr = (char*)malloc((strlen(wordbuffer)+1)*sizeof(char));
strcpy(newword->wordstr, newwordstr);
if(!firstword)
{
firstword = newword;
}
else
{
word* testword = firstword;
while(!testword->next)
{
testword = (testword->next);
}
testword->next = newword;
printf(newword->wordstr);
}
}
return 0;
}
}
else
{
return 1;
}
}
I attempted to remove the file reading part and replace it with a hard coded string, but the problem remained.
You might want to read about STL and use a list. Or use a C list, see a couple of examples,
Adding node in front of linklist
How to pop element from tail in linked list?
Trying to make linkedlist in C
Several problems. Fixed some. compiles.
I have annotated the code with places where you need to fix bounds checking, and the big problem was likely the strcpy to the struct word->wordstr uninitialized char*,
#include <stdio.h>
#include <cstring>
#include <cstdlib>
struct word;
struct nextword
{
word* sourceword;
word* next = 0;
};
int wordcount;
struct word
{
char* wordstr; //what do you think this pointer points to?
struct word* next = 0;
nextword* followingword = 0;
int nextwordcount = 0;
};
int main()
{
FILE* fh = NULL;
word* firstword = 0;
char* buffer = 0;
char* fname = "alice.txt";
long length = 0; //you did not initialize length
if ( (fh = fopen ("alice.txt", "rb")) )
{
//why not use fstat to get file size?
//why not use mmap to read file?
fseek (fh, 0, SEEK_END);
length = ftell (fh); //ok, length set here
fseek (fh, 0, SEEK_SET);
if( (buffer = (char *)malloc (length)) )
{
fread (buffer, 1, length, fh);
}
fclose (fh);
}
else
{
printf("error: cannot open %s",fname);
exit(1);
}
printf("read %s, %ld\n",fname,length);
if (!buffer)
{
printf("error: cannot open %s",fname);
exit(1);
//use exit, to return from main() //return 1;
}
//already checked buffer
{
int fileindex = 0;
//put wordbuffer after fileindex, avoids stackoverflow overwrite
char wordbuffer[500]; //500 bytes on stack, initialize?
memset(wordbuffer,0,sizeof(wordbuffer));
while(fileindex < length-1)
{
int wordindex = 0;
//several errors in this line, check for null terminator,
//check for newline, tab, basically any whitespace
//while(buffer[fileindex] != ' ')
while( buffer[fileindex] && buffer[fileindex] != ' ' )
{
wordbuffer[wordindex] = buffer[fileindex];
wordindex++;
fileindex++;
//here is another error, do not overflow your stack based buffer
if( wordindex>sizeof(buffer)-1 ) break; //do not overflow buffer
}
wordbuffer[wordindex] = '\0'; //terminate wordbuffer
//since you chose wordindex signed, you want it > 0
if(wordindex > 0)
{
//use a constructor
word* newword = (word*)malloc(sizeof(word));
//use a constructor
//or just use strdup, since it is just a cstring
char* newwordstr = strdup(wordbuffer);
//no, just set pointer to the above allocated string
//strcpy(newword->wordstr, newwordstr);
newword->wordstr = newwordstr;
if(!firstword)
{
firstword = newword;
}
else
{
word* testword = firstword;
while(!testword->next)
{
testword = (testword->next);
}
testword->next = newword;
printf(newword->wordstr);
}
}
return 0;
}
}
exit(0); //done
}
This compiles and runs without error, you need to look up linked list handling. You should implement a linked list, and then add word elements to list.
I'am trying to convert string "aÜ" from UTF-8 to CP1251 via C++ library iconv.h using TRANSLIT and as a result I get string "a?", when expecting "aU".
When I use php script <?php echo iconv("UTF-8", "Windows-1251//TRANSLIT", "Ü");> on this computer, I get "aU" string as result.
Here's the code:
#include <cstdlib>
#include <iconv.h>
#include <locale.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
using namespace std;
int IConvert(char *buf,char *outbuf, size_t len, const char *from, const char *to)
{
iconv_t iconv_cd;
if ((iconv_cd = iconv_open(to, from)) == (iconv_t) -1) {
printf("Cannot open iconv from %s to %s\n", from, to);
return 0;
}
char *inbuf = buf;
size_t inlen = len;
size_t outlen = len;
size_t res = 0;
while (inlen > 0 && outlen > 0) {
res = iconv(iconv_cd, &inbuf, &inlen, &outbuf, &outlen);
if (res == 0)
break;
if (res == (size_t) (-1)) {
if (errno != EILSEQ && errno != EINVAL) {
iconv_close(iconv_cd);
*outbuf = '\0';
printf("Erorr %s (%s)\n", strerror(errno), from);
return 0;
} else if (inbuf < outbuf) {
iconv_close(iconv_cd);
*outbuf = '\0';
printf("Erorr %s (inbuf < outbuf)\n", strerror(errno));
return 0;
}
}
if (inlen > 0 && outlen > 0) {
*outbuf++ = *inbuf++;
inlen--;
outlen--;
}
}
iconv_close(iconv_cd);
*outbuf = '\0';
return 1;
}
int main(int argc, char** argv) {
char* line = "\u00C0a\u00DC";
char* from = (char*) malloc(strlen(from)+1);
char* to = (char*) malloc(strlen(from)+1);
strcpy(from, line);
printf("%s\n", from);
IConvert(from, to, strlen(from)+1, "UTF-8", "CP1251//TRANSLIT");
printf("%s\n", to);
return 0;
}
Any idea what problem could be?
The solution is
setlocale(LC_ALL, "");
at the beginning of your program. Yes, the locale influences the transliteration. In a German locale Ü would be transliterated to UE not U.
I'm using "readline" library to create a console interface for my program. I'm able to autocomplete words using tab, but when I have words that share the same prefix like (car, card, carbon) it always chooses the shortest one. Here's my program (mostly taken from link):
#include <readline/readline.h>
#include <readline/history.h>
#include <stdlib.h>
#include <iostream>
const char *words[] = {"add", "remove", "rm", "update", "child", "children", "wife", "wifes"};
void *xmalloc (int size)
{
void *buf;
buf = malloc (size);
if (!buf)
{
fprintf (stderr, "Error: Out of memory. Exiting.\n");
exit (1);
}
return buf;
}
char *dupstr (const char *str)
{
char *temp;
temp = (char *) xmalloc (strlen (str) + 1);
strcpy (temp, str);
return (temp);
}
char *my_generator (const char *text, int state)
{
static int list_index, len;
const char *name;
if (!state)
{
list_index = 0;
len = strlen (text);
}
while (name = words[list_index])
{
list_index++;
if (strncmp (name, text, len) == 0) return dupstr (name);
}
// If no names matched, then return NULL.
return ((char *) NULL);
}
static char **my_completion (const char *text, int start, int end)
{
char **matches = (char **) NULL;
if (start == 0)
{
matches = rl_completion_matches ((char *) text, &my_generator);
}
else rl_bind_key ('\t', rl_abort);
return matches;
}
int main (int argc, char *argv[])
{
char *buf;
rl_attempted_completion_function = my_completion;
while ((buf = readline(">> ")) != NULL)
{
rl_bind_key ('\t', rl_complete);
if (strcmp (buf, "exit") == 0) break;
else if (buf[0] == '\0') continue;
else
{
std::cout << buf << std::endl;
add_history (buf);
}
}
free (buf);
return 0;
}
Is it possible to list all matches on double tab just like in ubuntu terminal?
I managed to get it to work by commenting out these two lines:
rl_bind_key ('\t', rl_complete);
and:
else rl_bind_key ('\t', rl_abort);
The default completion behaviour of readline works exactly like in ubuntu terminal, one tab to complete and two tabs to list possible completions. Not sure though what's the default completion function that's binded with the tab key, from the documentation i thought it was rl_possible_completions but it didn't give the same results.
Also i added the following line to my_completion function to prevent adding space at the end of the matched word:
rl_completion_append_character = '\0';
I removed dupstrfunction it and replaced it with the native strdup function instead (this has nothing to do with the auto complete problem, it's just to remove unnecessary code).
This is the final code:
#include <readline/readline.h>
#include <readline/history.h>
#include <stdlib.h>
#include <iostream>
const char *words[] = {"add", "remove", "rm", "update", "child", "children", "wife", "wives"};
// Generator function for word completion.
char *my_generator (const char *text, int state)
{
static int list_index, len;
const char *name;
if (!state)
{
list_index = 0;
len = strlen (text);
}
while (name = words[list_index])
{
list_index++;
if (strncmp (name, text, len) == 0) return strdup (name);
}
// If no names matched, then return NULL.
return ((char *) NULL);
}
// Custom completion function
static char **my_completion (const char *text, int start, int end)
{
// This prevents appending space to the end of the matching word
rl_completion_append_character = '\0';
char **matches = (char **) NULL;
if (start == 0)
{
matches = rl_completion_matches ((char *) text, &my_generator);
}
// else rl_bind_key ('\t', rl_abort);
return matches;
}
int main (int argc, char *argv[])
{
char *buf;
rl_attempted_completion_function = my_completion;
while ((buf = readline(">> ")) != NULL)
{
// rl_bind_key ('\t', rl_complete);
if (strcmp (buf, "exit") == 0) break;
else if (buf[0] == '\0')
{
free (buf);
continue;
}
else
{
std::cout << buf << std::endl;
add_history (buf);
}
free (buf);
buf = NULL;
}
if (buf != NULL) free (buf);
return 0;
}
The answer by razzak is almost correct, but this NULL must be added at the end of array of strings:
const char *words[] = {"add", "remove", "rm", "update", "child", "children", "wife", "wives", NULL};
Some changes for nonwarning compilation in my_generator() function:
while ((name = words[list_index++]))
{
if (strncmp (name, text, len) == 0) return strdup (name);
}
I need to get an access token (for a service account) for the google's OAuth authentication service. I tried several things an studied a lot of on the web but don't succeed.
Basically i followed https://developers.google.com/accounts/docs/OAuth2ServiceAccount
What i have done (VS2013):
int _tmain(int argc, _TCHAR* argv[])
{
Json::Value jwt_header;
Json::Value jwt_claim_set;
std::string jwt_b64;
std::time_t t = std::time(NULL);
Json::FastWriter jfw;
Json::StyledWriter jsw;
/* Create jwt header */
jwt_header["alg"] = "RS256";
jwt_header["typ"] = "JWT";
std::cout << jsw.write(jwt_header);
/* Create jwt claim set */
jwt_claim_set["iss"] = "myid#developer.gserviceaccount.com"; /* service account email address */
jwt_claim_set["scope"] = "https://www.googleapis.com/auth/plus.me" /* scope of requested access token */;
jwt_claim_set["aud"] = "https://accounts.google.com/o/oauth2/token"; /* intended target of the assertion for an access token */
jwt_claim_set["iad"] = std::to_string(t); /* issued time */
jwt_claim_set["exp"] = std::to_string(t+3600); /* expire time*/
std::cout << jsw.write(jwt_claim_set);
/* create http POST request body */
/* for header */
std::string json_buffer;
std::string json_buffer1;
json_buffer = jfw.write(jwt_header);
json_buffer = json_buffer.substr(0, json_buffer.size() - 1);
json_buffer = base64_encode(reinterpret_cast<const unsigned char*>(json_buffer.c_str()), json_buffer.length(), true); /* urlsafeBasic64 encode*/
json_buffer1.clear();
std::remove_copy(json_buffer.begin(), json_buffer.end(), std::back_inserter(json_buffer1), '=');
jwt_b64 = json_buffer1;
jwt_b64 += ".";
/* for claim set */
json_buffer = jfw.write(jwt_claim_set);
json_buffer = json_buffer.substr(0, json_buffer.size() - 1);
json_buffer = base64_encode(reinterpret_cast<const unsigned char*>(json_buffer.c_str()), json_buffer.length(), true); /* urlsafeBasic64 encode*/
json_buffer1.clear();
std::remove_copy(json_buffer.begin(), json_buffer.end(), std::back_inserter(json_buffer1), '=');
jwt_b64 += json_buffer1;
/* for signature */
std::string jwt_signature = jws_sign(jwt_b64, "key.p12");
if (!jwt_signature.empty())
{
jwt_b64 += ".";
json_buffer1.clear();
std::remove_copy(jwt_signature.begin(), jwt_signature.end(), std::back_inserter(json_buffer1), '=');
jwt_b64 += json_buffer1;
write2file("jwt.bat", jwt_b64); /* for test purpose calling with curl */
}
else
std::cout << "Error creating signature";
return 0;
}
int write2file(std::string filename, std::string data)
{
std::ofstream f(filename);
f << "%curl% -d \"grant_type=urn%%3Aietf%%3Aparams%%3Aoauth%%3Agrant-type%%3Ajwt-bearer&assertion=";
f << data;
f << "\" https://accounts.google.com/o/oauth2/token";
f.close();
return 0;
}
std::string jws_sign(std::string data, std::string pkcs12_path) {
SHA256_CTX mctx;
unsigned char hash[SHA256_DIGEST_LENGTH];
size_t hlen = SHA256_DIGEST_LENGTH;
const char *buf = data.c_str();
int n = strlen((const char*) buf);
SHA256_Init(&mctx);
SHA256_Update(&mctx, buf, n);
SHA256_Final(hash, &mctx);
std::string signature_b64;
unsigned char *sig = NULL;
size_t slen = 0;
EVP_PKEY_CTX *kctx;
EVP_PKEY *key = getPkey(pkcs12_path);
kctx = EVP_PKEY_CTX_new(key, NULL);
if (!kctx) goto err;
if (EVP_PKEY_sign_init(kctx) <= 0) goto err;
if (EVP_PKEY_CTX_set_rsa_padding(kctx, RSA_PKCS1_PADDING) <= 0) goto err;
if (EVP_PKEY_CTX_set_signature_md(kctx, EVP_sha256()) <= 0) goto err;
/* Determine buffer length */
if (EVP_PKEY_sign(kctx, NULL, &slen, hash, hlen) <= 0) goto err;
sig = (unsigned char *) OPENSSL_malloc(slen);
if (!sig) goto err;
if (EVP_PKEY_sign(kctx, sig, &slen, hash, hlen) <= 0) goto err;
signature_b64 = base64_encode(sig, (unsigned int)slen, true);
return signature_b64;
err:
/* Clean up */
EVP_cleanup();
signature_b64.clear();
return signature_b64;
}
All i receive back is
{
"error" : "invalid_grant"
}
So if someone can point me into the right direction would be great.
It would also help, if someone can point me to get the thing working by manually generating the jwt request out of openssl commands.
I'm working with VS2013
I found my mistake - was simply a typo :(
jwt_claim_set["iad"] = std::to_string(t); /* issued time */
needs to be
jwt_claim_set["iat"] = std::to_string(t); /* issued time */
The code works and generate valid token requests.
I've made a class for authentication on C++, will leave it here, may be someone may need it.
// YOU SHOULD GO TO Credentials SECTION FOR YOUR PROJECT AT https://console.developers.google.com/
// MAKE Service Account AND GET AUTHENTICATION JSON FROM IT,
// PLACE IT TO BUILD FOLDER AND CALL IT google_service_account.json
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <iomanip>
// SSL INCLUDES
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
// https://github.com/nlohmann/json
#include <nlohmann/json.hpp>
using json = nlohmann::json;
class TGoogleAuthCpp {
json serviceAccountJSON;
bool serviceAccountExists;
void readServiceAccountJson();
RSA* createPrivateRSA(std::string key);
bool RSASign( RSA* rsa,
const unsigned char* Msg,
size_t MsgLen,
unsigned char** EncMsg,
size_t* MsgLenEnc);
std::string signMessage(std::string privateKey, std::string plainText);
std::string url_encode(const std::string &value);
std::string base64_encode(const std::string &in);
public:
TGoogleAuthCpp();
int createRequest();
};
TGoogleAuthCpp::TGoogleAuthCpp() {
serviceAccountExists = false;
readServiceAccountJson();
}
RSA* TGoogleAuthCpp::createPrivateRSA(std::string key) {
RSA *rsa = NULL;
const char* c_string = key.c_str();
BIO * keybio = BIO_new_mem_buf((void*)c_string, -1);
if (keybio==NULL) {
return 0;
}
rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa,NULL, NULL);
return rsa;
}
bool TGoogleAuthCpp::RSASign( RSA* rsa,
const unsigned char* Msg,
size_t MsgLen,
unsigned char** EncMsg,
size_t* MsgLenEnc) {
EVP_MD_CTX* m_RSASignCtx = EVP_MD_CTX_create();
EVP_PKEY* priKey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(priKey, rsa);
if (EVP_DigestSignInit(m_RSASignCtx,NULL, EVP_sha256(), NULL,priKey)<=0) {
return false;
}
if (EVP_DigestSignUpdate(m_RSASignCtx, Msg, MsgLen) <= 0) {
return false;
}
if (EVP_DigestSignFinal(m_RSASignCtx, NULL, MsgLenEnc) <=0) {
return false;
}
*EncMsg = (unsigned char*)malloc(*MsgLenEnc);
if (EVP_DigestSignFinal(m_RSASignCtx, *EncMsg, MsgLenEnc) <= 0) {
return false;
}
EVP_MD_CTX_cleanup(m_RSASignCtx);
return true;
}
std::string TGoogleAuthCpp::signMessage(std::string privateKey, std::string plainText) {
RSA* privateRSA = createPrivateRSA(privateKey);
unsigned char* encMessage;
size_t encMessageLength;
RSASign(privateRSA, (unsigned char*) plainText.c_str(), plainText.length(), &encMessage, &encMessageLength);
std::string str1((char *)(encMessage), encMessageLength);
free(encMessage);
return base64_encode(str1);
}
void TGoogleAuthCpp::readServiceAccountJson() {
std::string fname = "google_service_account.json";
std::string line;
std::ifstream myfile (fname);
if (myfile.good()) {
std::stringstream ss;
if (myfile.is_open()) {
while (getline(myfile, line)) {
ss << line << '\n';
}
myfile.close();
serviceAccountJSON = json::parse(ss.str());
serviceAccountExists = true;
}
}
}
std::string TGoogleAuthCpp::base64_encode(const std::string &in) {
std::string out;
std::string base64_encode_b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";//=
int val=0, valb=-6;
for (unsigned char c : in) {
val = (val<<8) + c;
valb += 8;
while (valb>=0) {
out.push_back(base64_encode_b[(val>>valb)&0x3F]);
valb-=6;
}
}
if (valb>-6) out.push_back(base64_encode_b[((val<<8)>>(valb+8))&0x3F]);
while (out.size()%4) out.push_back('=');
return out;
}
std::string TGoogleAuthCpp::url_encode(const std::string &value) {
std::ostringstream escaped;
escaped.fill('0');
escaped << std::hex;
for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
std::string::value_type c = (*i);
// Keep alphanumeric and other accepted characters intact
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
escaped << c;
continue;
}
// Any other characters are percent-encoded
escaped << std::uppercase;
escaped << '%' << std::setw(2) << int((unsigned char) c);
escaped << std::nouppercase;
}
return escaped.str();
}
int TGoogleAuthCpp::createRequest() {
if (!serviceAccountExists) return 0;
json jwt_header;
json jwt_claim_set;
std::time_t t = std::time(NULL);
// Create jwt header
jwt_header["alg"] = "RS256";
jwt_header["typ"] = "JWT";
// Create jwt claim set
jwt_claim_set["iss"] = serviceAccountJSON["client_email"]; /* service account email address */
jwt_claim_set["scope"] = "https://www.googleapis.com/auth/androidpublisher" /* scope of requested access token */;
jwt_claim_set["aud"] = serviceAccountJSON["token_uri"]; /* intended target of the assertion for an access token */
jwt_claim_set["iat"] = t; /* issued time */
jwt_claim_set["exp"] = t+3600; /* expire time*/
// web token
std::stringstream jwt_ss;
// header
jwt_ss << base64_encode(jwt_header.dump());
jwt_ss << ".";
// claim set
jwt_ss << base64_encode(jwt_claim_set.dump());
// signature
std::string signed_msg = signMessage(serviceAccountJSON["private_key"], jwt_ss.str());
jwt_ss << "." << signed_msg;
std::stringstream post_body_ss;
post_body_ss << "curl -d '";
post_body_ss << "grant_type=" << url_encode("urn:ietf:params:oauth:grant-type:jwt-bearer");
post_body_ss << "&assertion=" << url_encode(jwt_ss.str());
post_body_ss << "' https://oauth2.googleapis.com/token";
std::string post_body = post_body_ss.str();
std::cout << post_body << std::endl;
return 1;
}
int main() {
TGoogleAuthCpp auth;
int res = auth.createRequest();
}