1 /* 2 curlx.c Authors: Peter Sylvester, Jean-Paul Merlin 3 4 This is a little program to demonstrate the usage of 5 6 - an ssl initialisation callback setting a user key and trustbases 7 coming from a pkcs12 file 8 - using an ssl application callback to find a URI in the 9 certificate presented during ssl session establishment. 10 11 */ 12 /* <DESC> 13 * demonstrates use of SSL context callback, requires OpenSSL 14 * </DESC> 15 */ 16 17 /* 18 * Copyright (c) 2003 The OpenEvidence Project. All rights reserved. 19 * 20 * Redistribution and use in source and binary forms, with or without 21 * modification, are permitted provided that the following conditions 22 * are met: 23 * 24 * 1. Redistributions of source code must retain the above copyright 25 * notice, this list of conditions, the following disclaimer, 26 * and the original OpenSSL and SSLeay Licences below. 27 * 28 * 2. Redistributions in binary form must reproduce the above copyright 29 * notice, this list of conditions, the following disclaimer 30 * and the original OpenSSL and SSLeay Licences below in 31 * the documentation and/or other materials provided with the 32 * distribution. 33 * 34 * 3. All advertising materials mentioning features or use of this 35 * software must display the following acknowledgments: 36 * "This product includes software developed by the Openevidence Project 37 * for use in the OpenEvidence Toolkit. (http://www.openevidence.org/)" 38 * This product includes software developed by the OpenSSL Project 39 * for use in the OpenSSL Toolkit (https://www.openssl.org/)" 40 * This product includes cryptographic software written by Eric Young 41 * (eay (at) cryptsoft.com). This product includes software written by Tim 42 * Hudson (tjh (at) cryptsoft.com)." 43 * 44 * 4. The names "OpenEvidence Toolkit" and "OpenEvidence Project" must not be 45 * used to endorse or promote products derived from this software without 46 * prior written permission. For written permission, please contact 47 * openevidence-core (at) openevidence.org. 48 * 49 * 5. Products derived from this software may not be called "OpenEvidence" 50 * nor may "OpenEvidence" appear in their names without prior written 51 * permission of the OpenEvidence Project. 52 * 53 * 6. Redistributions of any form whatsoever must retain the following 54 * acknowledgments: 55 * "This product includes software developed by the OpenEvidence Project 56 * for use in the OpenEvidence Toolkit (http://www.openevidence.org/) 57 * This product includes software developed by the OpenSSL Project 58 * for use in the OpenSSL Toolkit (https://www.openssl.org/)" 59 * This product includes cryptographic software written by Eric Young 60 * (eay (at) cryptsoft.com). This product includes software written by Tim 61 * Hudson (tjh (at) cryptsoft.com)." 62 * 63 * THIS SOFTWARE IS PROVIDED BY THE OpenEvidence PROJECT ``AS IS'' AND ANY 64 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 65 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 66 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenEvidence PROJECT OR 67 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 68 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 69 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 70 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 71 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 72 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 73 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 74 * OF THE POSSIBILITY OF SUCH DAMAGE. 75 * ==================================================================== 76 * 77 * This product includes software developed by the OpenSSL Project 78 * for use in the OpenSSL Toolkit (https://www.openssl.org/) 79 * This product includes cryptographic software written by Eric Young 80 * (eay (at) cryptsoft.com). This product includes software written by Tim 81 * Hudson (tjh (at) cryptsoft.com). 82 * 83 */ 84 85 #include <stdio.h> 86 #include <stdlib.h> 87 #include <string.h> 88 #include <curl/curl.h> 89 #include <openssl/x509v3.h> 90 #include <openssl/x509_vfy.h> 91 #include <openssl/crypto.h> 92 #include <openssl/lhash.h> 93 #include <openssl/objects.h> 94 #include <openssl/err.h> 95 #include <openssl/evp.h> 96 #include <openssl/x509.h> 97 #include <openssl/pkcs12.h> 98 #include <openssl/bio.h> 99 #include <openssl/ssl.h> 100 101 static const char *curlx_usage[]={ 102 "usage: curlx args\n", 103 " -p12 arg - tia file ", 104 " -envpass arg - environment variable which content the tia private" 105 " key password", 106 " -out arg - output file (response)- default stdout", 107 " -in arg - input file (request)- default stdin", 108 " -connect arg - URL of the server for the connection ex:" 109 " www.openevidence.org", 110 " -mimetype arg - MIME type for data in ex : application/timestamp-query" 111 " or application/dvcs -default application/timestamp-query", 112 " -acceptmime arg - MIME type acceptable for the response ex : " 113 "application/timestamp-response or application/dvcs -default none", 114 " -accesstype arg - an Object identifier in an AIA/SIA method, e.g." 115 " AD_DVCS or ad_timestamping", 116 NULL 117 }; 118 119 /* 120 121 ./curlx -p12 psy.p12 -envpass XX -in request -verbose -accesstype AD_DVCS 122 -mimetype application/dvcs -acceptmime application/dvcs -out response 123 124 */ 125 126 /* 127 * We use this ZERO_NULL to avoid picky compiler warnings, 128 * when assigning a NULL pointer to a function pointer var. 129 */ 130 131 #define ZERO_NULL 0 132 133 /* This is a context that we pass to all callbacks */ 134 135 typedef struct sslctxparm_st { 136 unsigned char *p12file; 137 const char *pst; 138 PKCS12 *p12; 139 EVP_PKEY *pkey; 140 X509 *usercert; 141 STACK_OF(X509) * ca; 142 CURL *curl; 143 BIO *errorbio; 144 int accesstype; 145 int verbose; 146 147 } sslctxparm; 148 149 /* some helper function. */ 150 151 static char *ia5string(ASN1_IA5STRING *ia5) 152 { 153 char *tmp; 154 if(!ia5 || !ia5->length) 155 return NULL; 156 tmp = OPENSSL_malloc(ia5->length + 1); 157 memcpy(tmp, ia5->data, ia5->length); 158 tmp[ia5->length] = 0; 159 return tmp; 160 } 161 162 /* A conveniance routine to get an access URI. */ 163 static unsigned char *my_get_ext(X509 *cert, const int type, 164 int extensiontype) 165 { 166 int i; 167 STACK_OF(ACCESS_DESCRIPTION) * accessinfo; 168 accessinfo = X509_get_ext_d2i(cert, extensiontype, NULL, NULL); 169 170 if(!sk_ACCESS_DESCRIPTION_num(accessinfo)) 171 return NULL; 172 for(i = 0; i < sk_ACCESS_DESCRIPTION_num(accessinfo); i++) { 173 ACCESS_DESCRIPTION * ad = sk_ACCESS_DESCRIPTION_value(accessinfo, i); 174 if(OBJ_obj2nid(ad->method) == type) { 175 if(ad->location->type == GEN_URI) { 176 return ia5string(ad->location->d.ia5); 177 } 178 return NULL; 179 } 180 } 181 return NULL; 182 } 183 184 /* This is an application verification call back, it does not 185 perform any addition verification but tries to find a URL 186 in the presented certificat. If found, this will become 187 the URL to be used in the POST. 188 */ 189 190 static int ssl_app_verify_callback(X509_STORE_CTX *ctx, void *arg) 191 { 192 sslctxparm * p = (sslctxparm *) arg; 193 int ok; 194 195 if(p->verbose > 2) 196 BIO_printf(p->errorbio, "entering ssl_app_verify_callback\n"); 197 198 ok = X509_verify_cert(ctx); 199 if(ok && ctx->cert) { 200 unsigned char *accessinfo; 201 if(p->verbose > 1) 202 X509_print_ex(p->errorbio, ctx->cert, 0, 0); 203 204 accessinfo = my_get_ext(ctx->cert, p->accesstype, NID_sinfo_access); 205 if(accessinfo) { 206 if(p->verbose) 207 BIO_printf(p->errorbio, "Setting URL from SIA to: %s\n", accessinfo); 208 209 curl_easy_setopt(p->curl, CURLOPT_URL, accessinfo); 210 } 211 else if(accessinfo = my_get_ext(ctx->cert, p->accesstype, 212 NID_info_access)) { 213 if(p->verbose) 214 BIO_printf(p->errorbio, "Setting URL from AIA to: %s\n", accessinfo); 215 216 curl_easy_setopt(p->curl, CURLOPT_URL, accessinfo); 217 } 218 } 219 if(p->verbose > 2) 220 BIO_printf(p->errorbio, "leaving ssl_app_verify_callback with %d\n", ok); 221 222 return ok; 223 } 224 225 226 /* The SSL initialisation callback. The callback sets: 227 - a private key and certificate 228 - a trusted ca certificate 229 - a preferred cipherlist 230 - an application verification callback (the function above) 231 */ 232 233 static CURLcode sslctxfun(CURL *curl, void *sslctx, void *parm) 234 { 235 sslctxparm *p = (sslctxparm *) parm; 236 SSL_CTX *ctx = (SSL_CTX *) sslctx; 237 238 if(!SSL_CTX_use_certificate(ctx, p->usercert)) { 239 BIO_printf(p->errorbio, "SSL_CTX_use_certificate problem\n"); 240 goto err; 241 } 242 if(!SSL_CTX_use_PrivateKey(ctx, p->pkey)) { 243 BIO_printf(p->errorbio, "SSL_CTX_use_PrivateKey\n"); 244 goto err; 245 } 246 247 if(!SSL_CTX_check_private_key(ctx)) { 248 BIO_printf(p->errorbio, "SSL_CTX_check_private_key\n"); 249 goto err; 250 } 251 252 SSL_CTX_set_quiet_shutdown(ctx, 1); 253 SSL_CTX_set_cipher_list(ctx, "RC4-MD5"); 254 SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); 255 256 X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx), 257 sk_X509_value(p->ca, sk_X509_num(p->ca)-1)); 258 259 SSL_CTX_set_verify_depth(ctx, 2); 260 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, ZERO_NULL); 261 SSL_CTX_set_cert_verify_callback(ctx, ssl_app_verify_callback, parm); 262 263 return CURLE_OK; 264 err: 265 ERR_print_errors(p->errorbio); 266 return CURLE_SSL_CERTPROBLEM; 267 268 } 269 270 int main(int argc, char **argv) 271 { 272 BIO* in = NULL; 273 BIO* out = NULL; 274 275 char *outfile = NULL; 276 char *infile = NULL; 277 278 int tabLength = 100; 279 char *binaryptr; 280 char *mimetype; 281 char *mimetypeaccept = NULL; 282 char *contenttype; 283 const char **pp; 284 unsigned char *hostporturl = NULL; 285 BIO *p12bio; 286 char **args = argv + 1; 287 unsigned char *serverurl; 288 sslctxparm p; 289 char *response; 290 291 CURLcode res; 292 struct curl_slist *headers = NULL; 293 int badarg = 0; 294 295 binaryptr = malloc(tabLength); 296 297 p.verbose = 0; 298 p.errorbio = BIO_new_fp(stderr, BIO_NOCLOSE); 299 300 curl_global_init(CURL_GLOBAL_DEFAULT); 301 302 /* we need some more for the P12 decoding */ 303 304 OpenSSL_add_all_ciphers(); 305 OpenSSL_add_all_digests(); 306 ERR_load_crypto_strings(); 307 308 while(*args && *args[0] == '-') { 309 if(!strcmp (*args, "-in")) { 310 if(args[1]) { 311 infile = *(++args); 312 } 313 else 314 badarg = 1; 315 } 316 else if(!strcmp (*args, "-out")) { 317 if(args[1]) { 318 outfile = *(++args); 319 } 320 else 321 badarg = 1; 322 } 323 else if(!strcmp (*args, "-p12")) { 324 if(args[1]) { 325 p.p12file = *(++args); 326 } 327 else 328 badarg = 1; 329 } 330 else if(strcmp(*args, "-envpass") == 0) { 331 if(args[1]) { 332 p.pst = getenv(*(++args)); 333 } 334 else 335 badarg = 1; 336 } 337 else if(strcmp(*args, "-connect") == 0) { 338 if(args[1]) { 339 hostporturl = *(++args); 340 } 341 else 342 badarg = 1; 343 } 344 else if(strcmp(*args, "-mimetype") == 0) { 345 if(args[1]) { 346 mimetype = *(++args); 347 } 348 else 349 badarg = 1; 350 } 351 else if(strcmp(*args, "-acceptmime") == 0) { 352 if(args[1]) { 353 mimetypeaccept = *(++args); 354 } 355 else 356 badarg = 1; 357 } 358 else if(strcmp(*args, "-accesstype") == 0) { 359 if(args[1]) { 360 p.accesstype = OBJ_obj2nid(OBJ_txt2obj(*++args, 0)); 361 if(p.accesstype == 0) 362 badarg = 1; 363 } 364 else 365 badarg = 1; 366 } 367 else if(strcmp(*args, "-verbose") == 0) { 368 p.verbose++; 369 } 370 else 371 badarg = 1; 372 args++; 373 } 374 375 if(mimetype == NULL || mimetypeaccept == NULL) 376 badarg = 1; 377 378 if(badarg) { 379 for(pp = curlx_usage; (*pp != NULL); pp++) 380 BIO_printf(p.errorbio, "%s\n", *pp); 381 BIO_printf(p.errorbio, "\n"); 382 goto err; 383 } 384 385 /* set input */ 386 387 in = BIO_new(BIO_s_file()); 388 if(in == NULL) { 389 BIO_printf(p.errorbio, "Error setting input bio\n"); 390 goto err; 391 } 392 else if(infile == NULL) 393 BIO_set_fp(in, stdin, BIO_NOCLOSE|BIO_FP_TEXT); 394 else if(BIO_read_filename(in, infile) <= 0) { 395 BIO_printf(p.errorbio, "Error opening input file %s\n", infile); 396 BIO_free(in); 397 goto err; 398 } 399 400 /* set output */ 401 402 out = BIO_new(BIO_s_file()); 403 if(out == NULL) { 404 BIO_printf(p.errorbio, "Error setting output bio.\n"); 405 goto err; 406 } 407 else if(outfile == NULL) 408 BIO_set_fp(out, stdout, BIO_NOCLOSE|BIO_FP_TEXT); 409 else if(BIO_write_filename(out, outfile) <= 0) { 410 BIO_printf(p.errorbio, "Error opening output file %s\n", outfile); 411 BIO_free(out); 412 goto err; 413 } 414 415 416 p.errorbio = BIO_new_fp(stderr, BIO_NOCLOSE); 417 418 p.curl = curl_easy_init(); 419 if(!p.curl) { 420 BIO_printf(p.errorbio, "Cannot init curl lib\n"); 421 goto err; 422 } 423 424 p12bio = BIO_new_file(p.p12file, "rb"); 425 if(!p12bio) { 426 BIO_printf(p.errorbio, "Error opening P12 file %s\n", p.p12file); 427 goto err; 428 } 429 p.p12 = d2i_PKCS12_bio(p12bio, NULL); 430 if(!p.p12) { 431 BIO_printf(p.errorbio, "Cannot decode P12 structure %s\n", p.p12file); 432 goto err; 433 } 434 435 p.ca = NULL; 436 if(!(PKCS12_parse (p.p12, p.pst, &(p.pkey), &(p.usercert), &(p.ca) ) )) { 437 BIO_printf(p.errorbio, "Invalid P12 structure in %s\n", p.p12file); 438 goto err; 439 } 440 441 if(sk_X509_num(p.ca) <= 0) { 442 BIO_printf(p.errorbio, "No trustworthy CA given.%s\n", p.p12file); 443 goto err; 444 } 445 446 if(p.verbose > 1) 447 X509_print_ex(p.errorbio, p.usercert, 0, 0); 448 449 /* determine URL to go */ 450 451 if(hostporturl) { 452 size_t len = strlen(hostporturl) + 9; 453 serverurl = malloc(len); 454 snprintf(serverurl, len, "https://%s", hostporturl); 455 } 456 else if(p.accesstype != 0) { /* see whether we can find an AIA or SIA for a 457 given access type */ 458 serverurl = my_get_ext(p.usercert, p.accesstype, NID_info_access); 459 if(!serverurl) { 460 int j = 0; 461 BIO_printf(p.errorbio, "no service URL in user cert " 462 "cherching in others certificats\n"); 463 for(j = 0; j<sk_X509_num(p.ca); j++) { 464 serverurl = my_get_ext(sk_X509_value(p.ca, j), p.accesstype, 465 NID_info_access); 466 if(serverurl) 467 break; 468 serverurl = my_get_ext(sk_X509_value(p.ca, j), p.accesstype, 469 NID_sinfo_access); 470 if(serverurl) 471 break; 472 } 473 } 474 } 475 476 if(!serverurl) { 477 BIO_printf(p.errorbio, "no service URL in certificats," 478 " check '-accesstype (AD_DVCS | ad_timestamping)'" 479 " or use '-connect'\n"); 480 goto err; 481 } 482 483 if(p.verbose) 484 BIO_printf(p.errorbio, "Service URL: <%s>\n", serverurl); 485 486 curl_easy_setopt(p.curl, CURLOPT_URL, serverurl); 487 488 /* Now specify the POST binary data */ 489 490 curl_easy_setopt(p.curl, CURLOPT_POSTFIELDS, binaryptr); 491 curl_easy_setopt(p.curl, CURLOPT_POSTFIELDSIZE, (long)tabLength); 492 493 /* pass our list of custom made headers */ 494 495 contenttype = malloc(15 + strlen(mimetype)); 496 snprintf(contenttype, 15 + strlen(mimetype), "Content-type: %s", mimetype); 497 headers = curl_slist_append(headers, contenttype); 498 curl_easy_setopt(p.curl, CURLOPT_HTTPHEADER, headers); 499 500 if(p.verbose) 501 BIO_printf(p.errorbio, "Service URL: <%s>\n", serverurl); 502 503 { 504 FILE *outfp; 505 BIO_get_fp(out, &outfp); 506 curl_easy_setopt(p.curl, CURLOPT_WRITEDATA, outfp); 507 } 508 509 res = curl_easy_setopt(p.curl, CURLOPT_SSL_CTX_FUNCTION, sslctxfun); 510 511 if(res != CURLE_OK) 512 BIO_printf(p.errorbio, "%d %s=%d %d\n", __LINE__, 513 "CURLOPT_SSL_CTX_FUNCTION", CURLOPT_SSL_CTX_FUNCTION, res); 514 515 curl_easy_setopt(p.curl, CURLOPT_SSL_CTX_DATA, &p); 516 517 { 518 int lu; int i = 0; 519 while((lu = BIO_read(in, &binaryptr[i], tabLength-i)) >0) { 520 i += lu; 521 if(i == tabLength) { 522 tabLength += 100; 523 binaryptr = realloc(binaryptr, tabLength); /* should be more careful */ 524 } 525 } 526 tabLength = i; 527 } 528 /* Now specify the POST binary data */ 529 530 curl_easy_setopt(p.curl, CURLOPT_POSTFIELDS, binaryptr); 531 curl_easy_setopt(p.curl, CURLOPT_POSTFIELDSIZE, (long)tabLength); 532 533 534 /* Perform the request, res will get the return code */ 535 536 BIO_printf(p.errorbio, "%d %s %d\n", __LINE__, "curl_easy_perform", 537 res = curl_easy_perform(p.curl)); 538 { 539 int result = curl_easy_getinfo(p.curl, CURLINFO_CONTENT_TYPE, &response); 540 if(mimetypeaccept && p.verbose) { 541 if(!strcmp(mimetypeaccept, response)) 542 BIO_printf(p.errorbio, "the response has a correct mimetype : %s\n", 543 response); 544 else 545 BIO_printf(p.errorbio, "the response doesn\'t have an acceptable " 546 "mime type, it is %s instead of %s\n", 547 response, mimetypeaccept); 548 } 549 } 550 551 /*** code d'erreur si accept mime ***, egalement code return HTTP != 200 ***/ 552 553 /* free the header list*/ 554 555 curl_slist_free_all(headers); 556 557 /* always cleanup */ 558 curl_easy_cleanup(p.curl); 559 560 BIO_free(in); 561 BIO_free(out); 562 return (EXIT_SUCCESS); 563 564 err: BIO_printf(p.errorbio, "error"); 565 exit(1); 566 } 567