/* * OAuth string functions in POSIX-C. * * Copyright 2007-2011 Robin Gareus * * The base64 functions are by Jan-Henrik Haukeland, * and un/escape_url() was inspired by libcurl's curl_escape under ISC-license * many thanks to Daniel Stenberg . * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ #if HAVE_CONFIG_H # include #endif #define WIPE_MEMORY ///< overwrite sensitve data before free()ing it. #include #include #include #include #include #include #include // isxdigit #include "xmalloc.h" #include "oauth.h" #ifndef WIN32 // getpid() on POSIX systems #include #include #else #define snprintf _snprintf #define strncasecmp strnicmp #endif /** * Base64 encode one byte */ char oauth_b64_encode(unsigned char u) { if(u < 26) return 'A'+u; if(u < 52) return 'a'+(u-26); if(u < 62) return '0'+(u-52); if(u == 62) return '+'; return '/'; } /** * Decode a single base64 character. */ unsigned char oauth_b64_decode(char c) { if(c >= 'A' && c <= 'Z') return(c - 'A'); if(c >= 'a' && c <= 'z') return(c - 'a' + 26); if(c >= '0' && c <= '9') return(c - '0' + 52); if(c == '+') return 62; return 63; } /** * Return TRUE if 'c' is a valid base64 character, otherwise FALSE */ int oauth_b64_is_base64(char c) { if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '+') || (c == '/') || (c == '=')) { return 1; } return 0; } /** * Base64 encode and return size data in 'src'. The caller must free the * returned string. * * @param size The size of the data in src * @param src The data to be base64 encode * @return encoded string otherwise NULL */ char *oauth_encode_base64(int size, const unsigned char *src) { int i; char *out, *p; if(!src) return NULL; if(!size) size= strlen((char *)src); out= (char*) xcalloc(sizeof(char), size*4/3+4); p= out; for(i=0; i>2; b5= ((b1&0x3)<<4)|(b2>>4); b6= ((b2&0xf)<<2)|(b3>>6); b7= b3&0x3f; *p++= oauth_b64_encode(b4); *p++= oauth_b64_encode(b5); if(i+1>4) ); if(c3 != '=') *p++=(((b2&0xf)<<4)|(b3>>2) ); if(c4 != '=') *p++=(((b3&0x3)<<6)|b4 ); } free(buf); dest[p-dest]='\0'; return(p-dest); } return 0; } /** * Escape 'string' according to RFC3986 and * http://oauth.net/core/1.0/#encoding_parameters. * * @param string The data to be encoded * @return encoded string otherwise NULL * The caller must free the returned string. */ char *oauth_url_escape(const char *string) { size_t alloc, newlen; char *ns = NULL, *testing_ptr = NULL; unsigned char in; size_t strindex=0; size_t length; if (!string) return xstrdup(""); alloc = strlen(string)+1; newlen = alloc; ns = (char*) xmalloc(alloc); length = alloc-1; while(length--) { in = *string; switch(in){ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': case '~': case '.': case '-': ns[strindex++]=in; break; default: newlen += 2; /* this'll become a %XX */ if(newlen > alloc) { alloc *= 2; testing_ptr = (char*) xrealloc(ns, alloc); ns = testing_ptr; } snprintf(&ns[strindex], 4, "%%%02X", in); strindex+=3; break; } string++; } ns[strindex]=0; return ns; } #ifndef ISXDIGIT # define ISXDIGIT(x) (isxdigit((int) ((unsigned char)x))) #endif /** * Parse RFC3986 encoded 'string' back to unescaped version. * * @param string The data to be unescaped * @param olen unless NULL the length of the returned string is stored there. * @return decoded string or NULL * The caller must free the returned string. */ char *oauth_url_unescape(const char *string, size_t *olen) { size_t alloc, strindex=0; char *ns = NULL; unsigned char in; long hex; if (!string) return NULL; alloc = strlen(string)+1; ns = (char*) xmalloc(alloc); while(--alloc > 0) { in = *string; if(('%' == in) && ISXDIGIT(string[1]) && ISXDIGIT(string[2])) { char hexstr[3]; // '%XX' hexstr[0] = string[1]; hexstr[1] = string[2]; hexstr[2] = 0; hex = strtol(hexstr, NULL, 16); in = (unsigned char)hex; /* hex is always < 256 */ string+=2; alloc-=2; } ns[strindex++] = in; string++; } ns[strindex]=0; if(olen) *olen = strindex; return ns; } /** * returns plaintext signature for the given key. * * the returned string needs to be freed by the caller * * @param m message to be signed * @param k key used for signing * @return signature string */ char *oauth_sign_plaintext (const char *m, const char *k) { return(oauth_url_escape(k)); } /** * encode strings and concatenate with '&' separator. * The number of strings to be concatenated must be * given as first argument. * all arguments thereafter must be of type (char *) * * @param len the number of arguments to follow this parameter * @param ... string to escape and added (may be NULL) * * @return pointer to memory holding the concatenated * strings - needs to be free(d) by the caller. or NULL * in case we ran out of memory. */ char *oauth_catenc(int len, ...) { va_list va; int i; char *rv = (char*) xmalloc(sizeof(char)); *rv='\0'; va_start(va, len); for(i=0;i0)?1:0); if(rv) len+=strlen(rv); rv=(char*) xrealloc(rv,len*sizeof(char)); if(i>0) strcat(rv, "&"); strcat(rv, enc); free(enc); } va_end(va); return(rv); } /** * splits the given url into a parameter array. * (see \ref oauth_serialize_url and \ref oauth_serialize_url_parameters for the reverse) * * NOTE: Request-parameters-values may include an ampersand character. * However if unescaped this function will use them as parameter delimiter. * If you need to make such a request, this function since version 0.3.5 allows * to use the ASCII SOH (0x01) character as alias for '&' (0x26). * (the motivation is convenience: SOH is /untypeable/ and much more * unlikely to appear than '&' - If you plan to sign fancy URLs you * should not split a query-string, but rather provide the parameter array * directly to \ref oauth_serialize_url) * * @param url the url or query-string to parse. * @param argv pointer to a (char *) array where the results are stored. * The array is re-allocated to match the number of parameters and each * parameter-string is allocated with strdup. - The memory needs to be freed * by the caller. * @param qesc use query parameter escape (vs post-param-escape) - if set * to 1 all '+' are treated as spaces ' ' * * @return number of parameter(s) in array. */ int oauth_split_post_paramters(const char *url, char ***argv, short qesc) { int argc=0; char *token, *tmp, *t1; if (!argv) return 0; if (!url) return 0; t1=xstrdup(url); // '+' represents a space, in a URL query string while ((qesc&1) && (tmp=strchr(t1,'+'))) *tmp=' '; tmp=t1; while((token=strtok(tmp,"&?"))) { if(!strncasecmp("oauth_signature=",token,16)) continue; (*argv)=(char**) xrealloc(*argv,sizeof(char*)*(argc+1)); while (!(qesc&2) && (tmp=strchr(token,'\001'))) *tmp='&'; if (argc>0 || (qesc&4)) (*argv)[argc]=oauth_url_unescape(token, NULL); else (*argv)[argc]=xstrdup(token); if (argc==0 && strstr(token, ":/")) { // HTTP does not allow empty absolute paths, so the URL // 'http://example.com' is equivalent to 'http://example.com/' and should // be treated as such for the purposes of OAuth signing (rfc2616, section 3.2.1) // see http://groups.google.com/group/oauth/browse_thread/thread/c44b6f061bfd98c?hl=en char *slash=strstr(token, ":/"); while (slash && *(++slash) == '/') ; // skip slashes eg /xxx:[\/]*/ #if 0 // skip possibly unescaped slashes in the userinfo - they're not allowed by RFC2396 but have been seen. // the hostname/IP may only contain alphanumeric characters - so we're safe there. if (slash && strchr(slash,'@')) slash=strchr(slash,'@'); #endif if (slash && !strchr(slash,'/')) { #ifdef DEBUG_OAUTH fprintf(stderr, "\nliboauth: added trailing slash to URL: '%s'\n\n", token); #endif free((*argv)[argc]); (*argv)[argc]= (char*) xmalloc(sizeof(char)*(2+strlen(token))); strcpy((*argv)[argc],token); strcat((*argv)[argc],"/"); } } if (argc==0 && (tmp=strstr((*argv)[argc],":80/"))) { memmove(tmp, tmp+3, strlen(tmp+2)); } tmp=NULL; argc++; } free(t1); return argc; } int oauth_split_url_parameters(const char *url, char ***argv) { return oauth_split_post_paramters(url, argv, 1); } /** * build a url query string from an array. * * @param argc the total number of elements in the array * @param start element in the array at which to start concatenating. * @param argv parameter-array to concatenate. * @return url string needs to be freed by the caller. * */ char *oauth_serialize_url (int argc, int start, char **argv) { return oauth_serialize_url_sep( argc, start, argv, "&", 0); } /** * encode query parameters from an array. * * @param argc the total number of elements in the array * @param start element in the array at which to start concatenating. * @param argv parameter-array to concatenate. * @param sep separator for parameters (usually "&") * @param mod - bitwise modifiers: * 1: skip all values that start with "oauth_" * 2: skip all values that don't start with "oauth_" * 4: add double quotation marks around values (use with sep=", " to generate HTTP Authorization header). * @return url string needs to be freed by the caller. */ char *oauth_serialize_url_sep (int argc, int start, char **argv, char *sep, int mod) { char *tmp, *t1; int i; int first=1; int seplen=strlen(sep); char *query = (char*) xmalloc(sizeof(char)); *query='\0'; for(i=start; i< argc; i++) { int len = 0; if ((mod&1)==1 && (strncmp(argv[i],"oauth_",6) == 0 || strncmp(argv[i],"x_oauth_",8) == 0) ) continue; if ((mod&2)==2 && (strncmp(argv[i],"oauth_",6) != 0 && strncmp(argv[i],"x_oauth_",8) != 0) && i!=0) continue; if (query) len+=strlen(query); if (i==start && i==0 && strstr(argv[i], ":/")) { tmp=xstrdup(argv[i]); #if 1 // encode white-space in the base-url while ((t1=strchr(tmp,' '))) { # if 0 *t1='+'; # else size_t off = t1-tmp; char *t2 = (char*) xmalloc(sizeof(char)*(3+strlen(tmp))); strcpy(t2, tmp); strcpy(t2+off+2, tmp+off); *(t2+off)='%'; *(t2+off+1)='2'; *(t2+off+2)='0'; free(tmp); tmp=t2; # endif #endif } len+=strlen(tmp); } else if(!(t1=strchr(argv[i], '='))) { // see http://oauth.net/core/1.0/#anchor14 // escape parameter names and arguments but not the '=' tmp=xstrdup(argv[i]); tmp=(char*) xrealloc(tmp,(strlen(tmp)+2)*sizeof(char)); strcat(tmp,"="); len+=strlen(tmp); } else { *t1=0; tmp = oauth_url_escape(argv[i]); *t1='='; t1 = oauth_url_escape((t1+1)); tmp=(char*) xrealloc(tmp,(strlen(tmp)+strlen(t1)+2+(mod&4?2:0))*sizeof(char)); strcat(tmp,"="); if (mod&4) strcat(tmp,"\""); strcat(tmp,t1); if (mod&4) strcat(tmp,"\""); free(t1); len+=strlen(tmp); } len+=seplen+1; query=(char*) xrealloc(query,len*sizeof(char)); strcat(query, ((i==start||first)?"":sep)); first=0; strcat(query, tmp); if (i==start && i==0 && strstr(tmp, ":/")) { strcat(query, "?"); first=1; } free(tmp); } return (query); } /** * build a query parameter string from an array. * * This function is a shortcut for \ref oauth_serialize_url (argc, 1, argv). * It strips the leading host/path, which is usually the first * element when using oauth_split_url_parameters on an URL. * * @param argc the total number of elements in the array * @param argv parameter-array to concatenate. * @return url string needs to be freed by the caller. */ char *oauth_serialize_url_parameters (int argc, char **argv) { return oauth_serialize_url(argc, 1, argv); } /** * generate a random string between 15 and 32 chars length * and return a pointer to it. The value needs to be freed by the * caller * * @return zero terminated random string. */ #if !defined HAVE_OPENSSL_HMAC_H && !defined USE_NSS /* pre liboauth-0.7.2 and possible future versions that don't use OpenSSL or NSS */ char *oauth_gen_nonce() { char *nc; static int rndinit = 1; const char *chars = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789_"; unsigned int max = strlen( chars ); int i, len; if(rndinit) {srand(time(NULL) #ifndef WIN32 // quick windows check. * getpid() #endif ); rndinit=0;} // seed random number generator - FIXME: we can do better ;) len=15+floor(rand()*16.0/(double)RAND_MAX); nc = (char*) xmalloc((len+1)*sizeof(char)); for(i=0;i # define MY_RAND RAND_bytes # define MY_SRAND ; #endif char *oauth_gen_nonce() { char *nc; unsigned char buf; const char *chars = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789_"; unsigned int max = strlen(chars); int i, len; MY_SRAND MY_RAND(&buf, 1); len=15+(((short)buf)&0x0f); nc = (char*) xmalloc((len+1)*sizeof(char)); for(i=0;il && !strncmp(argv[i],key,l) && argv[i][l] == '=') return 1; return 0; } /** * add query parameter to array * * @param argcp pointer to array length int * @param argvp pointer to array values * @param addparam parameter to add (eg. "foo=bar") */ void oauth_add_param_to_array(int *argcp, char ***argvp, const char *addparam) { (*argvp)=(char**) xrealloc(*argvp,sizeof(char*)*((*argcp)+1)); (*argvp)[(*argcp)++]= (char*) xstrdup(addparam); } /** * */ void oauth_add_protocol(int *argcp, char ***argvp, OAuthMethod method, const char *c_key, //< consumer key - posted plain text const char *t_key //< token key - posted plain text in URL ){ char oarg[1024]; // add OAuth specific arguments if (!oauth_param_exists(*argvp,*argcp,"oauth_nonce")) { char *tmp; snprintf(oarg, 1024, "oauth_nonce=%s", (tmp=oauth_gen_nonce())); oauth_add_param_to_array(argcp, argvp, oarg); free(tmp); } if (!oauth_param_exists(*argvp,*argcp,"oauth_timestamp")) { snprintf(oarg, 1024, "oauth_timestamp=%li", (long int) time(NULL)); oauth_add_param_to_array(argcp, argvp, oarg); } if (t_key) { snprintf(oarg, 1024, "oauth_token=%s", t_key); oauth_add_param_to_array(argcp, argvp, oarg); } snprintf(oarg, 1024, "oauth_consumer_key=%s", c_key); oauth_add_param_to_array(argcp, argvp, oarg); snprintf(oarg, 1024, "oauth_signature_method=%s", method==0?"HMAC-SHA1":method==1?"RSA-SHA1":"PLAINTEXT"); oauth_add_param_to_array(argcp, argvp, oarg); if (!oauth_param_exists(*argvp,*argcp,"oauth_version")) { snprintf(oarg, 1024, "oauth_version=1.0"); oauth_add_param_to_array(argcp, argvp, oarg); } #if 0 // oauth_version 1.0 Rev A if (!oauth_param_exists(argv,argc,"oauth_callback")) { snprintf(oarg, 1024, "oauth_callback=oob"); oauth_add_param_to_array(argcp, argvp, oarg); } #endif } char *oauth_sign_url (const char *url, char **postargs, OAuthMethod method, const char *c_key, //< consumer key - posted plain text const char *c_secret, //< consumer secret - used as 1st part of secret-key const char *t_key, //< token key - posted plain text in URL const char *t_secret //< token secret - used as 2st part of secret-key ) { return oauth_sign_url2(url, postargs, method, NULL, c_key, c_secret, t_key, t_secret); } char *oauth_sign_url2 (const char *url, char **postargs, OAuthMethod method, const char *http_method, //< HTTP request method const char *c_key, //< consumer key - posted plain text const char *c_secret, //< consumer secret - used as 1st part of secret-key const char *t_key, //< token key - posted plain text in URL const char *t_secret //< token secret - used as 2st part of secret-key ) { int argc; char **argv = NULL; char *rv; if (postargs) argc = oauth_split_post_paramters(url, &argv, 0); else argc = oauth_split_url_parameters(url, &argv); rv=oauth_sign_array2(&argc, &argv, postargs, method, http_method, c_key, c_secret, t_key, t_secret); oauth_free_array(&argc, &argv); return(rv); } char *oauth_sign_array (int *argcp, char***argvp, char **postargs, OAuthMethod method, const char *c_key, //< consumer key - posted plain text const char *c_secret, //< consumer secret - used as 1st part of secret-key const char *t_key, //< token key - posted plain text in URL const char *t_secret //< token secret - used as 2st part of secret-key ) { return oauth_sign_array2 (argcp, argvp, postargs, method, NULL, c_key, c_secret, t_key, t_secret); } void oauth_sign_array2_process (int *argcp, char***argvp, char **postargs, OAuthMethod method, const char *http_method, //< HTTP request method const char *c_key, //< consumer key - posted plain text const char *c_secret, //< consumer secret - used as 1st part of secret-key const char *t_key, //< token key - posted plain text in URL const char *t_secret //< token secret - used as 2st part of secret-key ) { char oarg[1024]; char *query; char *okey, *odat, *sign; char *http_request_method; if (!http_method) { http_request_method = xstrdup(postargs?"POST":"GET"); } else { int i; http_request_method = xstrdup(http_method); for (i=0;i