annotate src/c/openid.c @ 15:35bc4da563dd

Realms; documentation and license
author Adam Chlipala <adam@chlipala.net>
date Sun, 02 Jan 2011 11:22:30 -0500
parents 6b2a44da71b0
children ee97bc0e08fa
rev   line source
adam@14 1 #include <ctype.h>
adam@1 2 #include <string.h>
adam@1 3
adam@6 4 #include <openssl/bio.h>
adam@6 5 #include <openssl/evp.h>
adam@6 6 #include <openssl/buffer.h>
adam@0 7 #include <openssl/sha.h>
adam@7 8 #include <openssl/hmac.h>
adam@8 9 #include <openssl/dh.h>
adam@0 10 #include <curl/curl.h>
adam@1 11 #include <expat.h>
adam@0 12
adam@1 13 #include <openid.h>
adam@0 14
adam@3 15 #define BUF_MAX 10240
adam@3 16 #define BUF_INIT 1024
adam@3 17
adam@8 18 #define PRIME_LEN 64
adam@8 19 #define GENERATOR DH_GENERATOR_5
adam@2 20
adam@2 21 uw_Basis_string uw_OpenidFfi_endpoint(uw_context ctx, uw_OpenidFfi_discovery d) {
adam@8 22 return d.endpoint;
adam@2 23 }
adam@2 24
adam@2 25 uw_Basis_string uw_OpenidFfi_localId(uw_context ctx, uw_OpenidFfi_discovery d) {
adam@8 26 return d.localId;
adam@2 27 }
adam@2 28
adam@0 29 uw_unit uw_OpenidFfi_init(uw_context ctx) {
adam@0 30 curl_global_init(CURL_GLOBAL_ALL);
adam@0 31
adam@0 32 return uw_unit_v;
adam@0 33 }
adam@1 34
adam@1 35 static CURL *curl(uw_context ctx) {
adam@1 36 CURL *r;
adam@1 37
adam@1 38 if (!(r = uw_get_global(ctx, "curl"))) {
adam@1 39 r = curl_easy_init();
adam@1 40 if (r)
adam@1 41 uw_set_global(ctx, "curl", r, curl_easy_cleanup);
adam@1 42 }
adam@1 43
adam@1 44 return r;
adam@1 45 }
adam@1 46
adam@1 47 typedef struct {
adam@1 48 uw_context ctx;
adam@8 49 uw_OpenidFfi_discovery *d;
adam@1 50 } endpoint;
adam@1 51
adam@1 52 static void XMLCALL startElement(void *userData, const XML_Char *name, const XML_Char **atts) {
adam@1 53 endpoint *ep = userData;
adam@1 54
adam@1 55 if (!strcmp(name, "link")) {
adam@1 56 const XML_Char **attp;
adam@1 57 int found = 0;
adam@1 58
adam@1 59 for (attp = atts; *attp; attp += 2) {
adam@1 60 if (!strcmp(attp[0], "rel") && !strcmp(attp[1], "openid2.provider")) {
adam@1 61 found = 1;
adam@1 62 break;
adam@1 63 }
adam@1 64 }
adam@1 65
adam@1 66 if (found) {
adam@1 67 for (attp = atts; *attp; attp += 2) {
adam@1 68 if (!strcmp(attp[0], "href")) {
adam@2 69 ep->d->endpoint = uw_strdup(ep->ctx, attp[1]);
adam@1 70 return;
adam@1 71 }
adam@1 72 }
adam@1 73 }
adam@1 74 }
adam@1 75 }
adam@1 76
adam@1 77 typedef struct {
adam@1 78 XML_Parser parser;
adam@1 79 int any_errors;
adam@3 80 } curl_discovery_data;
adam@1 81
adam@3 82 static size_t write_discovery_data(void *buffer, size_t size, size_t nmemb, void *userp) {
adam@3 83 curl_discovery_data *d = userp;
adam@1 84
adam@1 85 if (!XML_Parse(d->parser, buffer, size * nmemb, 0))
adam@1 86 d->any_errors = 1;
adam@1 87
adam@1 88 return size * nmemb;
adam@1 89 }
adam@1 90
adam@2 91 uw_OpenidFfi_discovery *uw_OpenidFfi_discover(uw_context ctx, uw_Basis_string id) {
adam@1 92 char *s;
adam@1 93 CURL *c = curl(ctx);
adam@3 94 curl_discovery_data cd = {};
adam@8 95 uw_OpenidFfi_discovery *dy = uw_malloc(ctx, sizeof(uw_OpenidFfi_discovery));
adam@2 96 endpoint ep = {ctx, dy};
adam@1 97 CURLcode code;
adam@1 98
adam@2 99 dy->endpoint = dy->localId = NULL;
adam@2 100
adam@1 101 if (!strchr(id, ':')) {
adam@1 102 id = uw_Basis_strcat(ctx, "http://", id);
adam@1 103 if ((s = strchr(id, '#')) != NULL)
adam@1 104 *s = 0;
adam@1 105 } else if ((s = strchr(id, '#')) != NULL) {
adam@1 106 char *id2 = uw_malloc(ctx, s - id + 1);
adam@1 107 memcpy(id2, s, s - id);
adam@1 108 id2[s - id] = 0;
adam@1 109 id = id2;
adam@1 110 }
adam@1 111
adam@1 112 cd.parser = XML_ParserCreate(NULL);
adam@1 113 XML_SetUserData(cd.parser, &ep);
adam@1 114 uw_push_cleanup(ctx, (void (*)(void *))XML_ParserFree, cd.parser);
adam@7 115 XML_SetStartElementHandler(cd.parser, startElement);
adam@1 116
adam@1 117 curl_easy_setopt(c, CURLOPT_URL, id);
adam@3 118 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, write_discovery_data);
adam@1 119 curl_easy_setopt(c, CURLOPT_WRITEDATA, &cd);
adam@1 120
adam@1 121 code = curl_easy_perform(c);
adam@1 122 uw_pop_cleanup(ctx);
adam@1 123
adam@8 124 if (code || !dy->endpoint)
adam@1 125 return NULL;
adam@8 126 else
adam@8 127 return dy;
adam@1 128 }
adam@3 129
adam@3 130 uw_OpenidFfi_inputs uw_OpenidFfi_createInputs(uw_context ctx) {
adam@3 131 uw_buffer *r = uw_malloc(ctx, sizeof(uw_buffer));
adam@3 132 uw_buffer_init(BUF_MAX, r, BUF_INIT);
adam@3 133 return r;
adam@3 134 }
adam@3 135
adam@8 136 static void postify(uw_OpenidFfi_inputs buf, uw_Basis_string s) {
adam@14 137 char hex[4];
adam@14 138
adam@8 139 for (; *s; ++s) {
adam@14 140 if (isalnum(*s))
adam@14 141 uw_buffer_append(buf, s, 1);
adam@14 142 else {
adam@14 143 sprintf(hex, "%%%02X", (unsigned char)*s);
adam@14 144 uw_buffer_append(buf, hex, 3);
adam@14 145 }
adam@14 146
adam@14 147 /*switch (*s) {
adam@8 148 case '=':
adam@8 149 uw_buffer_append(buf, "%3D", 3);
adam@8 150 break;
adam@8 151 case '&':
adam@8 152 uw_buffer_append(buf, "%26", 3);
adam@8 153 break;
adam@14 154 case '%':
adam@14 155 uw_buffer_append(buf, "%25", 3);
adam@14 156 break;
adam@8 157 default:
adam@8 158 uw_buffer_append(buf, s, 1);
adam@14 159 }*/
adam@8 160 }
adam@3 161 }
adam@3 162
adam@3 163 uw_unit uw_OpenidFfi_addInput(uw_context ctx, uw_OpenidFfi_inputs buf, uw_Basis_string key, uw_Basis_string value) {
adam@3 164 if (uw_buffer_used(buf) > 0)
adam@3 165 uw_buffer_append(buf, "&", 1);
adam@3 166
adam@14 167 uw_buffer_append(buf, key, strlen(key));
adam@3 168 uw_buffer_append(buf, "=", 1);
adam@8 169 postify(buf, value);
adam@3 170
adam@3 171 return uw_unit_v;
adam@3 172 }
adam@3 173
adam@3 174 uw_Basis_string uw_OpenidFfi_getOutput(uw_context ctx, uw_OpenidFfi_outputs buf, uw_Basis_string key) {
adam@3 175 char *s = buf->start;
adam@3 176
adam@3 177 for (; *s; s = strchr(strchr(s, 0)+1, 0)+1)
adam@3 178 if (!strcmp(key, s))
adam@3 179 return strchr(s, 0)+1;
adam@3 180
adam@3 181 return NULL;
adam@3 182 }
adam@3 183
adam@3 184 static size_t write_buffer_data(void *buffer, size_t size, size_t nmemb, void *userp) {
adam@3 185 uw_buffer *buf = userp;
adam@3 186
adam@3 187 uw_buffer_append(buf, buffer, size * nmemb);
adam@3 188
adam@3 189 return size * nmemb;
adam@3 190 }
adam@3 191
adam@3 192 const char curl_failure[] = "error\0Error fetching URL";
adam@3 193
adam@4 194 uw_OpenidFfi_outputs uw_OpenidFfi_direct(uw_context ctx, uw_Basis_string url, uw_OpenidFfi_inputs inps) {
adam@3 195 uw_buffer *buf = uw_malloc(ctx, sizeof(uw_buffer));
adam@3 196 CURL *c = curl(ctx);
adam@3 197 CURLcode code;
adam@3 198
adam@3 199 uw_buffer_init(BUF_MAX, buf, BUF_INIT);
adam@3 200
adam@3 201 uw_buffer_append(inps, "", 1);
adam@3 202
adam@3 203 curl_easy_setopt(c, CURLOPT_URL, url);
adam@3 204 curl_easy_setopt(c, CURLOPT_POSTFIELDS, inps->start);
adam@3 205 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, write_buffer_data);
adam@3 206 curl_easy_setopt(c, CURLOPT_WRITEDATA, buf);
adam@3 207
adam@3 208 code = curl_easy_perform(c);
adam@3 209
adam@3 210 uw_buffer_append(buf, "", 1);
adam@3 211
adam@3 212 if (code) {
adam@3 213 uw_buffer_reset(buf);
adam@3 214 uw_buffer_append(buf, curl_failure, sizeof curl_failure);
adam@3 215 } else {
adam@3 216 char *s;
adam@3 217
adam@3 218 s = buf->start;
adam@3 219 while (*s) {
adam@3 220 char *colon = strchr(s, ':'), *newline;
adam@3 221
adam@3 222 if (!colon) {
adam@3 223 *s = 0;
adam@3 224 break;
adam@3 225 }
adam@3 226
adam@7 227 *colon = 0;
adam@7 228
adam@3 229 newline = strchr(colon+1, '\n');
adam@3 230
adam@7 231 if (!newline)
adam@3 232 break;
adam@3 233
adam@3 234 *newline = 0;
adam@3 235 s = newline+1;
adam@3 236 }
adam@3 237 }
adam@3 238
adam@3 239 return buf;
adam@3 240 }
adam@4 241
adam@4 242 static uw_Basis_string deurl(uw_context ctx, uw_Basis_string s) {
adam@4 243 uw_Basis_string r = uw_malloc(ctx, strlen(s)), s2 = r;
adam@4 244
adam@4 245 for (; *s; ++s) {
adam@4 246 if (s[0] == '%' && s[1] && s[2]) {
adam@4 247 unsigned u;
adam@4 248
adam@4 249 sscanf(s+1, "%02x", &u);
adam@4 250 *s2++ = u;
adam@4 251 s += 2;
adam@4 252 } else
adam@4 253 *s2++ = *s;
adam@4 254 }
adam@4 255
adam@4 256 *s2 = 0;
adam@4 257 return r;
adam@4 258 }
adam@4 259
adam@4 260 uw_OpenidFfi_outputs uw_OpenidFfi_indirect(uw_context ctx, uw_Basis_string fields) {
adam@4 261 uw_OpenidFfi_outputs b = malloc(sizeof(uw_buffer));
adam@4 262
adam@4 263 uw_buffer_init(BUF_MAX, b, BUF_INIT);
adam@4 264
adam@6 265 fields = uw_strdup(ctx, fields);
adam@6 266
adam@4 267 while (*fields) {
adam@4 268 char *equal = strchr(fields, '='), *and, *s;
adam@4 269
adam@4 270 if (!equal)
adam@4 271 break;
adam@4 272
adam@4 273 *equal = 0;
adam@4 274 s = deurl(ctx, fields);
adam@4 275 uw_buffer_append(b, s, strlen(s));
adam@4 276 uw_buffer_append(b, "", 1);
adam@4 277
adam@4 278 and = strchr(equal+1, '&');
adam@4 279 if (and) {
adam@4 280 *and = 0;
adam@4 281 fields = and+1;
adam@4 282 } else
adam@4 283 fields = and = strchr(equal+1, 0);
adam@4 284 s = deurl(ctx, equal+1);
adam@4 285 uw_buffer_append(b, s, strlen(s));
adam@4 286 uw_buffer_append(b, "", 1);
adam@4 287 }
adam@4 288
adam@4 289 uw_buffer_append(b, "", 1);
adam@4 290 return b;
adam@4 291 }
adam@6 292
adam@6 293 static uw_Basis_string base64(uw_context ctx, unsigned char *input, int length) {
adam@6 294 BIO *bmem, *b64;
adam@6 295
adam@6 296 b64 = BIO_new(BIO_f_base64());
adam@7 297 BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
adam@6 298 bmem = BIO_new(BIO_s_mem());
adam@7 299 BIO_push(b64, bmem);
adam@6 300 BIO_write(b64, input, length);
adam@6 301 (void)BIO_flush(b64);
adam@6 302
adam@7 303 int len = BIO_ctrl_pending(bmem);
adam@7 304 char *buff = uw_malloc(ctx, len+1);
adam@7 305 BIO_read(bmem, buff, len);
adam@7 306 buff[len] = 0;
adam@6 307
adam@6 308 BIO_free_all(b64);
adam@6 309
adam@6 310 return buff;
adam@6 311 }
adam@6 312
adam@8 313 static int unbase64(unsigned char *input, int length, unsigned char *buffer, int bufferLength)
adam@7 314 {
adam@7 315 BIO *b64, *bmem;
adam@8 316 int n;
adam@6 317
adam@7 318 b64 = BIO_new(BIO_f_base64());
adam@7 319 BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
adam@7 320 bmem = BIO_new_mem_buf(input, length);
adam@7 321 BIO_push(b64, bmem);
adam@8 322 n = BIO_read(b64, buffer, bufferLength);
adam@6 323
adam@7 324 BIO_free_all(bmem);
adam@8 325
adam@8 326 return n;
adam@8 327 }
adam@8 328
adam@12 329 uw_Basis_string uw_OpenidFfi_hmac_sha1(uw_context ctx, uw_Basis_string key, uw_Basis_string data) {
adam@8 330 unsigned char keyBin[SHA_DIGEST_LENGTH], out[EVP_MAX_MD_SIZE];
adam@8 331 unsigned outLen;
adam@8 332
adam@8 333 unbase64((unsigned char *)key, strlen(key), keyBin, sizeof keyBin);
adam@8 334
adam@8 335 HMAC(EVP_sha1(), keyBin, sizeof keyBin, (unsigned char *)data, strlen(data), out, &outLen);
adam@8 336 return base64(ctx, out, outLen);
adam@6 337 }
adam@7 338
adam@12 339 uw_Basis_string uw_OpenidFfi_hmac_sha256(uw_context ctx, uw_Basis_string key, uw_Basis_string data) {
adam@7 340 unsigned char keyBin[SHA256_DIGEST_LENGTH], out[EVP_MAX_MD_SIZE];
adam@7 341 unsigned outLen;
adam@7 342
adam@7 343 unbase64((unsigned char *)key, strlen(key), keyBin, sizeof keyBin);
adam@7 344
adam@7 345 HMAC(EVP_sha256(), keyBin, sizeof keyBin, (unsigned char *)data, strlen(data), out, &outLen);
adam@7 346 return base64(ctx, out, outLen);
adam@7 347 }
adam@8 348
adam@8 349 static uw_Basis_string btwoc(uw_context ctx, const BIGNUM *n) {
adam@8 350 int len = BN_num_bytes(n), i;
adam@8 351 unsigned char bytes[len+1];
adam@8 352
adam@8 353 bytes[0] = 0;
adam@8 354 BN_bn2bin(n, bytes+1);
adam@8 355
adam@8 356 for (i = 1; i <= len; ++i)
adam@8 357 if (bytes[i]) {
adam@8 358 if (bytes[i] & 0x80)
adam@8 359 --i;
adam@8 360 break;
adam@8 361 }
adam@8 362
adam@8 363 if (i > len)
adam@8 364 i = len;
adam@8 365
adam@8 366 return base64(ctx, bytes+i, len+1-i);
adam@8 367 }
adam@8 368
adam@8 369 static BIGNUM *unbtwoc(uw_context ctx, uw_Basis_string s) {
adam@8 370 unsigned char bytes[1024];
adam@8 371 int len;
adam@8 372
adam@8 373 len = unbase64((unsigned char *)s, strlen(s), bytes, sizeof bytes);
adam@8 374 return BN_bin2bn(bytes, len, NULL);
adam@8 375 }
adam@8 376
adam@8 377 uw_Basis_string uw_OpenidFfi_modulus(uw_context ctx, uw_OpenidFfi_dh dh) {
adam@8 378 return btwoc(ctx, dh->p);
adam@8 379 }
adam@8 380
adam@8 381 uw_Basis_string uw_OpenidFfi_generator(uw_context ctx, uw_OpenidFfi_dh dh) {
adam@8 382 return btwoc(ctx, dh->g);
adam@8 383 }
adam@8 384
adam@8 385 uw_Basis_string uw_OpenidFfi_public(uw_context ctx, uw_OpenidFfi_dh dh) {
adam@8 386 return btwoc(ctx, dh->pub_key);
adam@8 387 }
adam@8 388
adam@8 389 static void free_DH(void *data, int will_retry) {
adam@8 390 DH *dh = data;
adam@8 391 DH_free(dh);
adam@8 392 }
adam@8 393
adam@8 394 uw_OpenidFfi_dh uw_OpenidFfi_generate(uw_context ctx) {
adam@8 395 DH *dh = DH_new();
adam@8 396
adam@8 397 uw_register_transactional(ctx, dh, NULL, NULL, free_DH);
adam@8 398
adam@8 399 DH_generate_parameters_ex(dh, PRIME_LEN, GENERATOR, NULL);
adam@8 400
adam@8 401 if (DH_generate_key(dh) != 1)
adam@8 402 uw_error(ctx, FATAL, "Diffie-Hellman key generation failed");
adam@8 403
adam@8 404 return dh;
adam@8 405 }
adam@8 406
adam@8 407 uw_Basis_string uw_OpenidFfi_compute(uw_context ctx, uw_OpenidFfi_dh dh, uw_Basis_string server_pub) {
adam@8 408 BIGNUM *bn = unbtwoc(ctx, server_pub);
adam@12 409 unsigned char secret[DH_size(dh)+1], *secretP;
adam@8 410 int size;
adam@8 411
adam@8 412 uw_push_cleanup(ctx, (void (*)(void *))BN_free, bn);
adam@8 413
adam@12 414 size = DH_compute_key(secret+1, bn, dh);
adam@8 415 if (size == -1)
adam@8 416 uw_error(ctx, FATAL, "Diffie-Hellman key computation failed");
adam@8 417
adam@8 418 uw_pop_cleanup(ctx);
adam@8 419
adam@12 420 if (size > 0 && (secret[1] & 0x80)) {
adam@12 421 secret[0] = 0;
adam@12 422 secretP = secret;
adam@12 423 ++size;
adam@12 424 } else
adam@12 425 secretP = secret+1;
adam@12 426
adam@12 427 return base64(ctx, secretP, size);
adam@8 428 }
adam@12 429
adam@12 430 uw_Basis_string uw_OpenidFfi_sha1(uw_context ctx, uw_Basis_string data) {
adam@12 431 unsigned char dataBin[128], out[EVP_MAX_MD_SIZE];
adam@12 432 int len;
adam@12 433
adam@12 434 len = unbase64((unsigned char *)data, strlen(data), dataBin, sizeof dataBin);
adam@12 435
adam@12 436 SHA1(dataBin, len, out);
adam@12 437 return base64(ctx, out, SHA_DIGEST_LENGTH);
adam@12 438 }
adam@12 439
adam@12 440 uw_Basis_string uw_OpenidFfi_sha256(uw_context ctx, uw_Basis_string data) {
adam@12 441 unsigned char dataBin[128], out[EVP_MAX_MD_SIZE];
adam@12 442 int len;
adam@12 443
adam@12 444 len = unbase64((unsigned char *)data, strlen(data), dataBin, sizeof dataBin);
adam@12 445
adam@12 446 SHA256(dataBin, len, out);
adam@12 447 return base64(ctx, out, SHA256_DIGEST_LENGTH);
adam@12 448 }
adam@12 449
adam@12 450 uw_Basis_string uw_OpenidFfi_xor(uw_context ctx, uw_Basis_string s1, uw_Basis_string s2) {
adam@12 451 unsigned char buf1[128], buf2[128], bufO[128];
adam@12 452 int len1, len2, i;
adam@12 453
adam@12 454 len1 = unbase64((unsigned char *)s1, strlen(s1), buf1, sizeof buf1);
adam@12 455 len2 = unbase64((unsigned char *)s2, strlen(s2), buf2, sizeof buf2);
adam@12 456
adam@12 457 for (i = 0; i < len1; ++i)
adam@12 458 bufO[i] = buf1[i] ^ buf2[i % len2];
adam@12 459
adam@12 460 return base64(ctx, bufO, len1);
adam@12 461 }
adam@13 462
adam@13 463 uw_OpenidFfi_inputs uw_OpenidFfi_remode(uw_context ctx, uw_OpenidFfi_outputs out, uw_Basis_string mode) {
adam@13 464 uw_OpenidFfi_inputs in = uw_OpenidFfi_createInputs(ctx);
adam@13 465 char *s;
adam@13 466
adam@13 467 for (s = out->start; *s; s = strchr(strchr(s, 0)+1, 0)+1)
adam@13 468 if (!strcmp("openid.mode", s))
adam@13 469 uw_OpenidFfi_addInput(ctx, in, "openid.mode", mode);
adam@13 470 else
adam@13 471 uw_OpenidFfi_addInput(ctx, in, s, strchr(s, 0)+1);
adam@13 472
adam@13 473 return in;
adam@13 474 }