Mercurial > openid
view src/c/openid.c @ 51:a984dc1c8954
Merge
author | Adam Chlipala <adam@chlipala.net> |
---|---|
date | Sun, 24 Jul 2011 10:51:35 -0400 |
parents | ba203b170476 |
children |
line wrap: on
line source
#include <ctype.h> #include <string.h> #include <openssl/bio.h> #include <openssl/evp.h> #include <openssl/buffer.h> #include <openssl/sha.h> #include <openssl/hmac.h> #include <openssl/dh.h> #include <curl/curl.h> #include <expat.h> #include <pthread.h> #include <openid.h> #define BUF_MAX 10240 #define BUF_INIT 1024 #define DEFAULT_PRIME "DCF93A0B883972EC0E19989AC5A2CE310E1D37717E8D9571BB7623731866E61EF75A2E27898B057F9891C2E27A639C3F29B60814581CD3B2CA3986D2683705577D45C2E7E52DC81C7A171876E5CEA74B1448BFDFAF18828EFD2519F14E45E3826634AF1949E5B535CC829A483B8A76223E5D490A257F05BDFF16F2FB22C583AB" #define DEFAULT_GENERATOR "2" static BIGNUM *default_prime, *default_generator; uw_Basis_string uw_OpenidFfi_endpoint(uw_context ctx, uw_OpenidFfi_discovery d) { return d.endpoint; } uw_Basis_string uw_OpenidFfi_localId(uw_context ctx, uw_OpenidFfi_discovery d) { return d.localId; } static pthread_mutex_t *locks; static void locking_function(int mode, int n, const char *file, int line) { if (mode & CRYPTO_LOCK) pthread_mutex_lock(&locks[n]); else pthread_mutex_unlock(&locks[n]); } uw_unit uw_OpenidFfi_init(uw_context ctx) { int nl = CRYPTO_num_locks(), i; locks = malloc(sizeof(pthread_mutex_t) * nl); for (i = 0; i < nl; ++i) pthread_mutex_init(&locks[i], NULL); CRYPTO_set_locking_callback(locking_function); curl_global_init(CURL_GLOBAL_ALL); BN_hex2bn(&default_prime, DEFAULT_PRIME); BN_dec2bn(&default_generator, DEFAULT_GENERATOR); return uw_unit_v; } static CURL *curl(uw_context ctx) { CURL *r; if (!(r = uw_get_global(ctx, "curl"))) { r = curl_easy_init(); if (r) uw_set_global(ctx, "curl", r, curl_easy_cleanup); } return r; } typedef enum { NONE, SERVICE, TYPE, MATCHED, URI } xrds_mode; typedef struct { uw_context ctx; uw_OpenidFfi_discovery *d; xrds_mode mode; int cur_priority, max_priority, found_old_version; } endpoint; static void XMLCALL startElement(void *userData, const XML_Char *name, const XML_Char **atts) { endpoint *ep = userData; if (!strncmp(name, "xrd:", 4)) name += 4; if (!strcmp(name, "link")) { const XML_Char **attp; int found = 0; for (attp = atts; *attp; attp += 2) { if (!strcmp(attp[0], "rel") && !strcmp(attp[1], "openid2.provider")) { found = 1; break; } else if (!strcmp(attp[0], "rel") && !strcmp(attp[1], "openid.server")) { ep->found_old_version = 1; return; } } if (found) { for (attp = atts; *attp; attp += 2) { if (!strcmp(attp[0], "href")) { ep->d->endpoint = uw_strdup(ep->ctx, attp[1]); return; } } } } else if (!strcmp(name, "Service")) { const XML_Char **attp; ep->cur_priority = 0; for (attp = atts; *attp; attp += 2) if (!strcmp(attp[0], "priority")) { ep->cur_priority = atoi(attp[1]); break; } ep->mode = SERVICE; } else if (!strcmp(name, "Type")) { if (ep->mode == SERVICE) ep->mode = TYPE; } else if (!strcmp(name, "URI")) { if (ep->mode == MATCHED) ep->mode = URI; } } static char server[] = "http://specs.openid.net/auth/2.0/server"; static char signon[] = "http://specs.openid.net/auth/2.0/signon"; static void XMLCALL cdata(void *userData, const XML_Char *s, int len) { endpoint *ep = userData; switch (ep->mode) { case TYPE: if ((len == sizeof(server)-1 && !memcmp(server, s, sizeof(server)-1)) || (len == sizeof(signon)-1 && !memcmp(signon, s, sizeof(signon)-1))) ep->mode = MATCHED; break; case URI: if (ep->cur_priority < ep->max_priority) { ep->d->endpoint = uw_malloc(ep->ctx, len+1); memcpy(ep->d->endpoint, s, len); ep->d->endpoint[len] = 0; ep->max_priority = ep->cur_priority; } break; default: break; } } static void XMLCALL endElement(void *userData, const XML_Char *name) { endpoint *ep = userData; if (!strncmp(name, "xrd:", 4)) name += 4; if (!strcmp(name, "Service")) ep->mode = NONE; else if (!strcmp(name, "Type")) { if (ep->mode != MATCHED) ep->mode = SERVICE; } else if (!strcmp(name, "URI")) ep->mode = MATCHED; } typedef struct { XML_Parser parser; int any_errors; } curl_discovery_data; static size_t write_discovery_data(void *buffer, size_t size, size_t nmemb, void *userp) { curl_discovery_data *d = userp; if (!XML_Parse(d->parser, buffer, size * nmemb, 0)) d->any_errors = 1; return size * nmemb; } uw_OpenidFfi_discovery *uw_OpenidFfi_discover(uw_context ctx, uw_Basis_string id) { char *s; CURL *c = curl(ctx); curl_discovery_data cd = {}; uw_OpenidFfi_discovery *dy = uw_malloc(ctx, sizeof(uw_OpenidFfi_discovery)); endpoint ep = {ctx, dy, NONE, 0, INT_MAX, 0}; CURLcode code; struct curl_slist *headers = NULL; dy->endpoint = dy->localId = NULL; if (!strchr(id, ':')) { id = uw_Basis_strcat(ctx, "http://", id); if ((s = strchr(id, '#')) != NULL) *s = 0; } else if ((s = strchr(id, '#')) != NULL) { char *id2 = uw_malloc(ctx, s - id + 1); memcpy(id2, s, s - id); id2[s - id] = 0; id = id2; } cd.parser = XML_ParserCreate(NULL); XML_SetUserData(cd.parser, &ep); uw_push_cleanup(ctx, (void (*)(void *))XML_ParserFree, cd.parser); XML_SetElementHandler(cd.parser, startElement, endElement); XML_SetCharacterDataHandler(cd.parser, cdata); curl_easy_reset(c); curl_easy_setopt(c, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3); curl_easy_setopt(c, CURLOPT_URL, id); curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, write_discovery_data); curl_easy_setopt(c, CURLOPT_WRITEDATA, &cd); curl_slist_append(headers, "Accept: application/xrds+xml"); uw_push_cleanup(ctx, (void (*)(void *))curl_slist_free_all, headers); curl_easy_setopt(c, CURLOPT_HTTPHEADER, headers); code = curl_easy_perform(c); uw_pop_cleanup(ctx); uw_pop_cleanup(ctx); if (code) { uw_Basis_debug(ctx, "CURL error:"); uw_Basis_debug(ctx, (char*)curl_easy_strerror(code)); } else if (!dy->endpoint) { if (ep.found_old_version) uw_Basis_debug(ctx, "Provider doesn't support OpenID version 2.0 but does support an older version"); else uw_Basis_debug(ctx, "Couldn't parse endpoint from page"); } if (code || !dy->endpoint) return NULL; else return dy; } uw_OpenidFfi_inputs uw_OpenidFfi_createInputs(uw_context ctx) { uw_buffer *r = uw_malloc(ctx, sizeof(uw_buffer)); uw_buffer_init(BUF_MAX, r, BUF_INIT); return r; } static void postify(uw_OpenidFfi_inputs buf, uw_Basis_string s) { char hex[4]; for (; *s; ++s) { if (isalnum(*s)) uw_buffer_append(buf, s, 1); else { sprintf(hex, "%%%02X", (unsigned char)*s); uw_buffer_append(buf, hex, 3); } } } uw_unit uw_OpenidFfi_addInput(uw_context ctx, uw_OpenidFfi_inputs buf, uw_Basis_string key, uw_Basis_string value) { if (uw_buffer_used(buf) > 0) uw_buffer_append(buf, "&", 1); uw_buffer_append(buf, key, strlen(key)); uw_buffer_append(buf, "=", 1); postify(buf, value); return uw_unit_v; } uw_Basis_string uw_OpenidFfi_getOutput(uw_context ctx, uw_OpenidFfi_outputs buf, uw_Basis_string key) { char *s = buf->start; for (; *s; s = strchr(strchr(s, 0)+1, 0)+1) if (!strcmp(key, s)) return strchr(s, 0)+1; return NULL; } uw_unit uw_OpenidFfi_printOutputs(uw_context ctx, uw_OpenidFfi_outputs buf) { char *s = buf->start; for (; *s; s = strchr(strchr(s, 0)+1, 0)+1) fprintf(stderr, "%s => %s\n", s, strchr(s, 0)+1); return uw_unit_v; } static size_t write_buffer_data(void *buffer, size_t size, size_t nmemb, void *userp) { uw_buffer *buf = userp; uw_buffer_append(buf, buffer, size * nmemb); return size * nmemb; } const char curl_failure[] = "error\0Error fetching URL"; uw_OpenidFfi_outputs uw_OpenidFfi_direct(uw_context ctx, uw_Basis_string url, uw_OpenidFfi_inputs inps) { uw_buffer *buf = uw_malloc(ctx, sizeof(uw_buffer)); CURL *c = curl(ctx); CURLcode code; uw_buffer_init(BUF_MAX, buf, BUF_INIT); uw_buffer_append(inps, "", 1); curl_easy_reset(c); curl_easy_setopt(c, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3); curl_easy_setopt(c, CURLOPT_URL, url); curl_easy_setopt(c, CURLOPT_POSTFIELDS, inps->start); curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, write_buffer_data); curl_easy_setopt(c, CURLOPT_WRITEDATA, buf); code = curl_easy_perform(c); uw_buffer_append(buf, "", 1); if (code) { uw_buffer_reset(buf); uw_buffer_append(buf, curl_failure, sizeof curl_failure); } else { char *s; s = buf->start; while (*s) { char *colon = strchr(s, ':'), *newline; if (!colon) { *s = 0; break; } *colon = 0; newline = strchr(colon+1, '\n'); if (!newline) break; *newline = 0; s = newline+1; } } return buf; } static uw_Basis_string deurl(uw_context ctx, uw_Basis_string s) { uw_Basis_string r = uw_malloc(ctx, strlen(s)), s2 = r; for (; *s; ++s) { if (s[0] == '%' && s[1] && s[2]) { unsigned u; sscanf(s+1, "%02x", &u); *s2++ = u; s += 2; } else *s2++ = *s; } *s2 = 0; return r; } uw_OpenidFfi_outputs uw_OpenidFfi_indirect(uw_context ctx, uw_Basis_string fields) { uw_OpenidFfi_outputs b = malloc(sizeof(uw_buffer)); uw_buffer_init(BUF_MAX, b, BUF_INIT); fields = uw_strdup(ctx, fields); while (*fields) { char *equal = strchr(fields, '='), *and, *s; if (!equal) break; *equal = 0; s = deurl(ctx, fields); uw_buffer_append(b, s, strlen(s)); uw_buffer_append(b, "", 1); and = strchr(equal+1, '&'); if (and) { *and = 0; fields = and+1; } else fields = and = strchr(equal+1, 0); s = deurl(ctx, equal+1); uw_buffer_append(b, s, strlen(s)); uw_buffer_append(b, "", 1); } uw_buffer_append(b, "", 1); return b; } static uw_Basis_string base64(uw_context ctx, unsigned char *input, int length) { BIO *bmem, *b64; b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); bmem = BIO_new(BIO_s_mem()); BIO_push(b64, bmem); BIO_write(b64, input, length); (void)BIO_flush(b64); int len = BIO_ctrl_pending(bmem); char *buff = uw_malloc(ctx, len+1); BIO_read(bmem, buff, len); buff[len] = 0; BIO_free_all(b64); return buff; } static int unbase64(unsigned char *input, int length, unsigned char *buffer, int bufferLength) { BIO *b64, *bmem; int n; b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); bmem = BIO_new_mem_buf(input, length); BIO_push(b64, bmem); n = BIO_read(b64, buffer, bufferLength); BIO_free_all(b64); return n; } uw_Basis_string uw_OpenidFfi_hmac_sha1(uw_context ctx, uw_Basis_string key, uw_Basis_string data) { unsigned char keyBin[SHA_DIGEST_LENGTH], out[EVP_MAX_MD_SIZE]; unsigned outLen; unbase64((unsigned char *)key, strlen(key), keyBin, sizeof keyBin); HMAC(EVP_sha1(), keyBin, sizeof keyBin, (unsigned char *)data, strlen(data), out, &outLen); return base64(ctx, out, outLen); } uw_Basis_string uw_OpenidFfi_hmac_sha256(uw_context ctx, uw_Basis_string key, uw_Basis_string data) { unsigned char keyBin[SHA256_DIGEST_LENGTH], out[EVP_MAX_MD_SIZE]; unsigned outLen; unbase64((unsigned char *)key, strlen(key), keyBin, sizeof keyBin); HMAC(EVP_sha256(), keyBin, sizeof keyBin, (unsigned char *)data, strlen(data), out, &outLen); return base64(ctx, out, outLen); } static uw_Basis_string btwoc(uw_context ctx, const BIGNUM *n) { int len = BN_num_bytes(n), i; unsigned char bytes[len+1]; bytes[0] = 0; BN_bn2bin(n, bytes+1); for (i = 1; i <= len; ++i) if (bytes[i]) { if (bytes[i] & 0x80) --i; break; } if (i > len) i = len; return base64(ctx, bytes+i, len+1-i); } static BIGNUM *unbtwoc(uw_context ctx, uw_Basis_string s) { unsigned char bytes[1024]; int len; len = unbase64((unsigned char *)s, strlen(s), bytes, sizeof bytes); return BN_bin2bn(bytes, len, NULL); } uw_Basis_string uw_OpenidFfi_modulus(uw_context ctx, uw_OpenidFfi_dh dh) { return btwoc(ctx, dh->p); } uw_Basis_string uw_OpenidFfi_generator(uw_context ctx, uw_OpenidFfi_dh dh) { return btwoc(ctx, dh->g); } uw_Basis_string uw_OpenidFfi_public(uw_context ctx, uw_OpenidFfi_dh dh) { return btwoc(ctx, dh->pub_key); } static void free_DH(void *data, int will_retry) { DH *dh = data; dh->p = NULL; dh->g = NULL; DH_free(dh); } uw_OpenidFfi_dh uw_OpenidFfi_generate(uw_context ctx) { DH *dh = DH_new(); dh->p = default_prime; dh->g = default_generator; uw_register_transactional(ctx, dh, NULL, NULL, free_DH); if (DH_generate_key(dh) != 1) uw_error(ctx, FATAL, "Diffie-Hellman key generation failed"); return dh; } uw_Basis_string uw_OpenidFfi_compute(uw_context ctx, uw_OpenidFfi_dh dh, uw_Basis_string server_pub) { BIGNUM *bn = unbtwoc(ctx, server_pub); unsigned char secret[DH_size(dh)+1], *secretP; int size; uw_push_cleanup(ctx, (void (*)(void *))BN_free, bn); size = DH_compute_key(secret+1, bn, dh); if (size == -1) uw_error(ctx, FATAL, "Diffie-Hellman key computation failed"); uw_pop_cleanup(ctx); if (size > 0 && (secret[1] & 0x80)) { secret[0] = 0; secretP = secret; ++size; } else secretP = secret+1; return base64(ctx, secretP, size); } uw_Basis_string uw_OpenidFfi_sha1(uw_context ctx, uw_Basis_string data) { unsigned char dataBin[128], out[EVP_MAX_MD_SIZE]; int len; len = unbase64((unsigned char *)data, strlen(data), dataBin, sizeof dataBin); SHA1(dataBin, len, out); return base64(ctx, out, SHA_DIGEST_LENGTH); } uw_Basis_string uw_OpenidFfi_sha256(uw_context ctx, uw_Basis_string data) { unsigned char dataBin[128], out[EVP_MAX_MD_SIZE]; int len; len = unbase64((unsigned char *)data, strlen(data), dataBin, sizeof dataBin); SHA256(dataBin, len, out); return base64(ctx, out, SHA256_DIGEST_LENGTH); } uw_Basis_string uw_OpenidFfi_xor(uw_context ctx, uw_Basis_string s1, uw_Basis_string s2) { unsigned char buf1[128], buf2[128], bufO[128]; int len1, len2, i; len1 = unbase64((unsigned char *)s1, strlen(s1), buf1, sizeof buf1); len2 = unbase64((unsigned char *)s2, strlen(s2), buf2, sizeof buf2); for (i = 0; i < len1; ++i) bufO[i] = buf1[i] ^ buf2[i % len2]; return base64(ctx, bufO, len1); } uw_Basis_bool uw_OpenidFfi_secCmp(uw_context ctx, uw_Basis_string s1, uw_Basis_string s2) { return uw_streq(s1, s2); } uw_OpenidFfi_inputs uw_OpenidFfi_remode(uw_context ctx, uw_OpenidFfi_outputs out, uw_Basis_string mode) { uw_OpenidFfi_inputs in = uw_OpenidFfi_createInputs(ctx); char *s; for (s = out->start; *s; s = strchr(strchr(s, 0)+1, 0)+1) if (!strcmp("openid.mode", s)) uw_OpenidFfi_addInput(ctx, in, "openid.mode", mode); else uw_OpenidFfi_addInput(ctx, in, s, strchr(s, 0)+1); return in; }